1
2 /*
3 * Copyright (C) 2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package com.android.launcher3;
19
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator;
27 import android.annotation.TargetApi;
28 import android.app.Activity;
29 import android.app.ActivityManager;
30 import android.app.ActivityOptions;
31 import android.app.AlertDialog;
32 import android.app.SearchManager;
33 import android.appwidget.AppWidgetHostView;
34 import android.appwidget.AppWidgetManager;
35 import android.appwidget.AppWidgetProviderInfo;
36 import android.content.ActivityNotFoundException;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentCallbacks2;
39 import android.content.ComponentName;
40 import android.content.ContentResolver;
41 import android.content.Context;
42 import android.content.DialogInterface;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.content.SharedPreferences;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.ApplicationInfo;
48 import android.content.pm.PackageManager;
49 import android.content.pm.PackageManager.NameNotFoundException;
50 import android.content.res.Configuration;
51 import android.content.res.Resources;
52 import android.database.ContentObserver;
53 import android.graphics.Bitmap;
54 import android.graphics.Canvas;
55 import android.graphics.Color;
56 import android.graphics.Point;
57 import android.graphics.PorterDuff;
58 import android.graphics.Rect;
59 import android.graphics.drawable.Drawable;
60 import android.net.Uri;
61 import android.os.AsyncTask;
62 import android.os.Build;
63 import android.os.Bundle;
64 import android.os.Environment;
65 import android.os.Handler;
66 import android.os.Message;
67 import android.os.StrictMode;
68 import android.os.SystemClock;
69 import android.speech.RecognizerIntent;
70 import android.text.Selection;
71 import android.text.SpannableStringBuilder;
72 import android.text.TextUtils;
73 import android.text.method.TextKeyListener;
74 import android.util.DisplayMetrics;
75 import android.util.Log;
76 import android.view.Display;
77 import android.view.Gravity;
78 import android.view.HapticFeedbackConstants;
79 import android.view.KeyEvent;
80 import android.view.LayoutInflater;
81 import android.view.Menu;
82 import android.view.MotionEvent;
83 import android.view.Surface;
84 import android.view.View;
85 import android.view.View.OnClickListener;
86 import android.view.View.OnLongClickListener;
87 import android.view.ViewAnimationUtils;
88 import android.view.ViewGroup;
89 import android.view.ViewTreeObserver;
90 import android.view.Window;
91 import android.view.WindowManager;
92 import android.view.accessibility.AccessibilityEvent;
93 import android.view.animation.AccelerateInterpolator;
94 import android.view.animation.DecelerateInterpolator;
95 import android.view.inputmethod.InputMethodManager;
96 import android.widget.Advanceable;
97 import android.widget.FrameLayout;
98 import android.widget.ImageView;
99 import android.widget.TextView;
100 import android.widget.Toast;
101
102 import com.android.launcher3.DropTarget.DragObject;
103 import com.android.launcher3.PagedView.PageSwitchListener;
104 import com.android.launcher3.compat.AppWidgetManagerCompat;
105 import com.android.launcher3.compat.LauncherActivityInfoCompat;
106 import com.android.launcher3.compat.LauncherAppsCompat;
107 import com.android.launcher3.compat.PackageInstallerCompat;
108 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
109 import com.android.launcher3.compat.UserHandleCompat;
110 import com.android.launcher3.compat.UserManagerCompat;
111
112 import java.io.DataInputStream;
113 import java.io.DataOutputStream;
114 import java.io.File;
115 import java.io.FileDescriptor;
116 import java.io.FileNotFoundException;
117 import java.io.FileOutputStream;
118 import java.io.IOException;
119 import java.io.PrintWriter;
120 import java.lang.reflect.Field;
121 import java.lang.reflect.InvocationTargetException;
122 import java.lang.reflect.Method;
123 import java.text.DateFormat;
124 import java.util.ArrayList;
125 import java.util.Collection;
126 import java.util.Date;
127 import java.util.HashMap;
128 import java.util.List;
129 import java.util.concurrent.atomic.AtomicInteger;
130
131 /**
132 * Default launcher application.
133 */
134 public class Launcher extends Activity
135 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
136 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
137 static final String TAG = "Launcher";
138 static final boolean LOGD = false;
139
140 static final boolean PROFILE_STARTUP = false;
141 static final boolean DEBUG_WIDGETS = false;
142 static final boolean DEBUG_STRICT_MODE = false;
143 static final boolean DEBUG_RESUME_TIME = false;
144 static final boolean DEBUG_DUMP_LOG = false;
145
146 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
147
148 private static final int REQUEST_CREATE_SHORTCUT = 1;
149 private static final int REQUEST_CREATE_APPWIDGET = 5;
150 private static final int REQUEST_PICK_SHORTCUT = 7;
151 private static final int REQUEST_PICK_APPWIDGET = 9;
152 private static final int REQUEST_PICK_WALLPAPER = 10;
153
154 private static final int REQUEST_BIND_APPWIDGET = 11;
155 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
156
157 /**
158 * IntentStarter uses request codes starting with this. This must be greater than all activity
159 * request codes used internally.
160 */
161 protected static final int REQUEST_LAST = 100;
162
163 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
164
165 static final int SCREEN_COUNT = 5;
166 static final int DEFAULT_SCREEN = 2;
167
168 // To turn on these properties, type
169 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
170 static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
171 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
172 static final String DISABLE_ALL_APPS_PROPERTY = "launcher_noallapps";
173
174 // The Intent extra that defines whether to ignore the launch animation
175 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
176 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
177
178 // Type: int
179 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
180 // Type: int
181 private static final String RUNTIME_STATE = "launcher.state";
182 // Type: int
183 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
184 // Type: int
185 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
186 // Type: int
187 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
188 // Type: int
189 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
190 // Type: boolean
191 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
192 // Type: long
193 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
194 // Type: int
195 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
196 // Type: int
197 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
198 // Type: parcelable
199 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
200 // Type: parcelable
201 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
202 // Type: int[]
203 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
204
205 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
206 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
207
208 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
209 static final String ACTION_FIRST_LOAD_COMPLETE =
210 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
211
212 private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
213 private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
214 "com.android.launcher.toolbar_search_icon";
215 private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
216 "com.android.launcher.toolbar_voice_search_icon";
217
218 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
219 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
220
221 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
222
223 /** The different states that Launcher can be in. */
224 private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };
225 private State mState = State.WORKSPACE;
226 private AnimatorSet mStateAnimation;
227
228 private boolean mIsSafeModeEnabled;
229
230 static final int APPWIDGET_HOST_ID = 1024;
231 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
232 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
233 private static final int ACTIVITY_START_DELAY = 1000;
234
235 private static final Object sLock = new Object();
236 private static int sScreen = DEFAULT_SCREEN;
237
238 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
239 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
240
241 // How long to wait before the new-shortcut animation automatically pans the workspace
242 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
243 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
244 private static int NEW_APPS_ANIMATION_DELAY = 500;
245 private static final int SINGLE_FRAME_DELAY = 16;
246
247 private final BroadcastReceiver mCloseSystemDialogsReceiver
248 = new CloseSystemDialogsIntentReceiver();
249 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
250
251 private LayoutInflater mInflater;
252
253 private Workspace mWorkspace;
254 private View mLauncherView;
255 private View mPageIndicators;
256 private DragLayer mDragLayer;
257 private DragController mDragController;
258 private View mWeightWatcher;
259
260 private AppWidgetManagerCompat mAppWidgetManager;
261 private LauncherAppWidgetHost mAppWidgetHost;
262
263 private ItemInfo mPendingAddInfo = new ItemInfo();
264 private AppWidgetProviderInfo mPendingAddWidgetInfo;
265 private int mPendingAddWidgetId = -1;
266
267 private int[] mTmpAddItemCellCoordinates = new int[2];
268
269 private FolderInfo mFolderInfo;
270
271 private Hotseat mHotseat;
272 private ViewGroup mOverviewPanel;
273
274 private View mAllAppsButton;
275
276 private SearchDropTargetBar mSearchDropTargetBar;
277 private AppsCustomizeTabHost mAppsCustomizeTabHost;
278 private AppsCustomizePagedView mAppsCustomizeContent;
279 private boolean mAutoAdvanceRunning = false;
280 private View mQsb;
281
282 private Bundle mSavedState;
283 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
284 // scroll issues (because the workspace may not have been measured yet) and extra work.
285 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
286 private State mOnResumeState = State.NONE;
287
288 private SpannableStringBuilder mDefaultKeySsb = null;
289
290 private boolean mWorkspaceLoading = true;
291
292 private boolean mPaused = true;
293 private boolean mRestoring;
294 private boolean mWaitingForResult;
295 private boolean mOnResumeNeedsLoad;
296
297 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
298 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
299
300 private Bundle mSavedInstanceState;
301
302 private LauncherModel mModel;
303 private IconCache mIconCache;
304 private boolean mUserPresent = true;
305 private boolean mVisible = false;
306 private boolean mHasFocus = false;
307 private boolean mAttached = false;
308
309 private static LocaleConfiguration sLocaleConfiguration = null;
310
311 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
312
313 private View.OnTouchListener mHapticFeedbackTouchListener;
314
315 // Related to the auto-advancing of widgets
316 private final int ADVANCE_MSG = 1;
317 private final int mAdvanceInterval = 20000;
318 private final int mAdvanceStagger = 250;
319 private long mAutoAdvanceSentTime;
320 private long mAutoAdvanceTimeLeft = -1;
321 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
322 new HashMap<View, AppWidgetProviderInfo>();
323
324 // Determines how long to wait after a rotation before restoring the screen orientation to
325 // match the sensor state.
326 private final int mRestoreScreenOrientationDelay = 500;
327
328 // External icons saved in case of resource changes, orientation, etc.
329 private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
330 private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
331
332 private Drawable mWorkspaceBackgroundDrawable;
333
334 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
335 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
336
337 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
338 static Date sDateStamp = new Date();
339 static DateFormat sDateFormat =
340 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
341 static long sRunStart = System.currentTimeMillis();
342 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
343
344 // We only want to get the SharedPreferences once since it does an FS stat each time we get
345 // it from the context.
346 private SharedPreferences mSharedPrefs;
347
348 private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null;
349
350 // Holds the page that we need to animate to, and the icon views that we need to animate up
351 // when we scroll to that page on resume.
352 private ImageView mFolderIconImageView;
353 private Bitmap mFolderIconBitmap;
354 private Canvas mFolderIconCanvas;
355 private Rect mRectForFolderAnimation = new Rect();
356
357 private BubbleTextView mWaitingForResume;
358
359 private Runnable mBuildLayersRunnable = new Runnable() {
360 public void run() {
361 if (mWorkspace != null) {
362 mWorkspace.buildPageHardwareLayers();
363 }
364 }
365 };
366
367 private static PendingAddArguments sPendingAddItem;
368
369 public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
370
371 private static class PendingAddArguments {
372 int requestCode;
373 Intent intent;
374 long container;
375 long screenId;
376 int cellX;
377 int cellY;
378 int appWidgetId;
379 }
380
381 private Stats mStats;
382
383 FocusIndicatorView mFocusHandler;
384
385 static boolean isPropertyEnabled(String propertyName) {
386 return Log.isLoggable(propertyName, Log.VERBOSE);
387 }
388
389 @Override
390 protected void onCreate(Bundle savedInstanceState) {
391 if (DEBUG_STRICT_MODE) {
392 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
393 .detectDiskReads()
394 .detectDiskWrites()
395 .detectNetwork() // or .detectAll() for all detectable problems
396 .penaltyLog()
397 .build());
398 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
399 .detectLeakedSqlLiteObjects()
400 .detectLeakedClosableObjects()
401 .penaltyLog()
402 .penaltyDeath()
403 .build());
404 }
405
406 super.onCreate(savedInstanceState);
407
408 LauncherAppState.setApplicationContext(getApplicationContext());
409 LauncherAppState app = LauncherAppState.getInstance();
410 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
411 // Determine the dynamic grid properties
412 Point smallestSize = new Point();
413 Point largestSize = new Point();
414 Point realSize = new Point();
415 Display display = getWindowManager().getDefaultDisplay();
416 display.getCurrentSizeRange(smallestSize, largestSize);
417 display.getRealSize(realSize);
418 DisplayMetrics dm = new DisplayMetrics();
419 display.getMetrics(dm);
420
421 // Lazy-initialize the dynamic grid
422 DeviceProfile grid = app.initDynamicGrid(this,
423 Math.min(smallestSize.x, smallestSize.y),
424 Math.min(largestSize.x, largestSize.y),
425 realSize.x, realSize.y,
426 dm.widthPixels, dm.heightPixels);
427
428 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
429 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
430 Context.MODE_PRIVATE);
431 mIsSafeModeEnabled = getPackageManager().isSafeMode();
432 mModel = app.setLauncher(this);
433 mIconCache = app.getIconCache();
434 mIconCache.flushInvalidIcons(grid);
435 mDragController = new DragController(this);
436 mInflater = getLayoutInflater();
437
438 mStats = new Stats(this);
439
440 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
441
442 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
443 mAppWidgetHost.startListening();
444
445 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
446 // this also ensures that any synchronous binding below doesn't re-trigger another
447 // LauncherModel load.
448 mPaused = false;
449
450 if (PROFILE_STARTUP) {
451 android.os.Debug.startMethodTracing(
452 Environment.getExternalStorageDirectory() + "/launcher");
453 }
454
455 checkForLocaleChange();
456 setContentView(R.layout.launcher);
457
458 setupViews();
459 grid.layout(this);
460
461 registerContentObservers();
462
463 lockAllApps();
464
465 mSavedState = savedInstanceState;
466 restoreState(mSavedState);
467
468 if (PROFILE_STARTUP) {
469 android.os.Debug.stopMethodTracing();
470 }
471
472 if (!mRestoring) {
473 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
474 // If the user leaves launcher, then we should just load items asynchronously when
475 // they return.
476 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
477 } else {
478 // We only load the page synchronously if the user rotates (or triggers a
479 // configuration change) while launcher is in the foreground
480 mModel.startLoader(true, mWorkspace.getRestorePage());
481 }
482 }
483
484 // For handling default keys
485 mDefaultKeySsb = new SpannableStringBuilder();
486 Selection.setSelection(mDefaultKeySsb, 0);
487
488 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
489 registerReceiver(mCloseSystemDialogsReceiver, filter);
490
491 updateGlobalIcons();
492
493 // On large interfaces, we want the screen to auto-rotate based on the current orientation
494 unlockScreenOrientation(true);
495
496 if (shouldShowIntroScreen()) {
497 showIntroScreen();
498 } else {
499 showFirstRunActivity();
500 showFirstRunClings();
501 }
502 }
503
504 @Override
505 public void onLauncherProviderChange() { }
506
507 /** To be overriden by subclasses to hint to Launcher that we have custom content */
508 protected boolean hasCustomContentToLeft() {
509 return false;
510 }
511
512 /**
513 * To be overridden by subclasses to populate the custom content container and call
514 * {@link #addToCustomContentPage}. This will only be invoked if
515 * {@link #hasCustomContentToLeft()} is {@code true}.
516 */
517 protected void populateCustomContentContainer() {
518 }
519
520 /**
521 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
522 * ensure the custom content page is added or removed if necessary.
523 */
524 protected void invalidateHasCustomContentToLeft() {
525 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
526 // Not bound yet, wait for bindScreens to be called.
527 return;
528 }
529
530 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
531 // Create the custom content page and call the subclass to populate it.
532 mWorkspace.createCustomContentContainer();
533 populateCustomContentContainer();
534 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
535 mWorkspace.removeCustomContentPage();
536 }
537 }
538
539 private void updateGlobalIcons() {
540 boolean searchVisible = false;
541 boolean voiceVisible = false;
542 // If we have a saved version of these external icons, we load them up immediately
543 int coi = getCurrentOrientationIndexForGlobalIcons();
544 if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null) {
545 searchVisible = updateGlobalSearchIcon();
546 voiceVisible = updateVoiceSearchIcon(searchVisible);
547 }
548 if (sGlobalSearchIcon[coi] != null) {
549 updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
550 searchVisible = true;
551 }
552 if (sVoiceSearchIcon[coi] != null) {
553 updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
554 voiceVisible = true;
555 }
556 if (mSearchDropTargetBar != null) {
557 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
558 }
559 }
560
561 private void checkForLocaleChange() {
562 if (sLocaleConfiguration == null) {
563 new AsyncTask<Void, Void, LocaleConfiguration>() {
564 @Override
565 protected LocaleConfiguration doInBackground(Void... unused) {
566 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
567 readConfiguration(Launcher.this, localeConfiguration);
568 return localeConfiguration;
569 }
570
571 @Override
572 protected void onPostExecute(LocaleConfiguration result) {
573 sLocaleConfiguration = result;
574 checkForLocaleChange(); // recursive, but now with a locale configuration
575 }
576 }.execute();
577 return;
578 }
579
580 final Configuration configuration = getResources().getConfiguration();
581
582 final String previousLocale = sLocaleConfiguration.locale;
583 final String locale = configuration.locale.toString();
584
585 final int previousMcc = sLocaleConfiguration.mcc;
586 final int mcc = configuration.mcc;
587
588 final int previousMnc = sLocaleConfiguration.mnc;
589 final int mnc = configuration.mnc;
590
591 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
592
593 if (localeChanged) {
594 sLocaleConfiguration.locale = locale;
595 sLocaleConfiguration.mcc = mcc;
596 sLocaleConfiguration.mnc = mnc;
597
598 mIconCache.flush();
599
600 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
601 new AsyncTask<Void, Void, Void>() {
602 public Void doInBackground(Void ... args) {
603 writeConfiguration(Launcher.this, localeConfiguration);
604 return null;
605 }
606 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
607 }
608 }
609
610 private static class LocaleConfiguration {
611 public String locale;
612 public int mcc = -1;
613 public int mnc = -1;
614 }
615
616 private static void readConfiguration(Context context, LocaleConfiguration configuration) {
617 DataInputStream in = null;
618 try {
619 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFS));
620 configuration.locale = in.readUTF();
621 configuration.mcc = in.readInt();
622 configuration.mnc = in.readInt();
623 } catch (FileNotFoundException e) {
624 // Ignore
625 } catch (IOException e) {
626 // Ignore
627 } finally {
628 if (in != null) {
629 try {
630 in.close();
631 } catch (IOException e) {
632 // Ignore
633 }
634 }
635 }
636 }
637
638 private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
639 DataOutputStream out = null;
640 try {
641 out = new DataOutputStream(context.openFileOutput(
642 LauncherFiles.LAUNCHER_PREFS, MODE_PRIVATE));
643 out.writeUTF(configuration.locale);
644 out.writeInt(configuration.mcc);
645 out.writeInt(configuration.mnc);
646 out.flush();
647 } catch (FileNotFoundException e) {
648 // Ignore
649 } catch (IOException e) {
650 //noinspection ResultOfMethodCallIgnored
651 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFS).delete();
652 } finally {
653 if (out != null) {
654 try {
655 out.close();
656 } catch (IOException e) {
657 // Ignore
658 }
659 }
660 }
661 }
662
663 public Stats getStats() {
664 return mStats;
665 }
666
667 public LayoutInflater getInflater() {
668 return mInflater;
669 }
670
671 boolean isDraggingEnabled() {
672 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
673 // that is subsequently removed from the workspace in startBinding().
674 return !mModel.isLoadingWorkspace();
675 }
676
677 static int getScreen() {
678 synchronized (sLock) {
679 return sScreen;
680 }
681 }
682
683 static void setScreen(int screen) {
684 synchronized (sLock) {
685 sScreen = screen;
686 }
687 }
688
689 public static int generateViewId() {
690 if (Build.VERSION.SDK_INT >= 17) {
691 return View.generateViewId();
692 } else {
693 // View.generateViewId() is not available. The following fallback logic is a copy
694 // of its implementation.
695 for (;;) {
696 final int result = sNextGeneratedId.get();
697 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
698 int newValue = result + 1;
699 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
700 if (sNextGeneratedId.compareAndSet(result, newValue)) {
701 return result;
702 }
703 }
704 }
705 }
706
707 public int getViewIdForItem(ItemInfo info) {
708 // This cast is safe given the > 2B range for int.
709 int itemId = (int) info.id;
710 if (mItemIdToViewId.containsKey(itemId)) {
711 return mItemIdToViewId.get(itemId);
712 }
713 int viewId = generateViewId();
714 mItemIdToViewId.put(itemId, viewId);
715 return viewId;
716 }
717
718 /**
719 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
720 * a configuration step, this allows the proper animations to run after other transitions.
721 */
722 private long completeAdd(PendingAddArguments args) {
723 long screenId = args.screenId;
724 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
725 // When the screen id represents an actual screen (as opposed to a rank) we make sure
726 // that the drop page actually exists.
727 screenId = ensurePendingDropLayoutExists(args.screenId);
728 }
729
730 switch (args.requestCode) {
731 case REQUEST_CREATE_SHORTCUT:
732 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
733 args.cellY);
734 break;
735 case REQUEST_CREATE_APPWIDGET:
736 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
737 break;
738 case REQUEST_RECONFIGURE_APPWIDGET:
739 completeRestoreAppWidget(args.appWidgetId);
740 break;
741 }
742 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
743 // if you turned the screen off and then back while in All Apps, Launcher would not
744 // return to the workspace. Clearing mAddInfo.container here fixes this issue
745 resetAddInfo();
746 return screenId;
747 }
748
749 @Override
750 protected void onActivityResult(
751 final int requestCode, final int resultCode, final Intent data) {
752 // Reset the startActivity waiting flag
753 setWaitingForResult(false);
754 final int pendingAddWidgetId = mPendingAddWidgetId;
755 mPendingAddWidgetId = -1;
756
757 Runnable exitSpringLoaded = new Runnable() {
758 @Override
759 public void run() {
760 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
761 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
762 }
763 };
764
765 if (requestCode == REQUEST_BIND_APPWIDGET) {
766 final int appWidgetId = data != null ?
767 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
768 if (resultCode == RESULT_CANCELED) {
769 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
770 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
771 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
772 } else if (resultCode == RESULT_OK) {
773 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
774 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
775 }
776 return;
777 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
778 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
779 mWorkspace.exitOverviewMode(false);
780 }
781 return;
782 }
783
784 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
785 requestCode == REQUEST_CREATE_APPWIDGET);
786
787 final boolean workspaceLocked = isWorkspaceLocked();
788 // We have special handling for widgets
789 if (isWidgetDrop) {
790 final int appWidgetId;
791 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
792 : -1;
793 if (widgetId < 0) {
794 appWidgetId = pendingAddWidgetId;
795 } else {
796 appWidgetId = widgetId;
797 }
798
799 final int result;
800 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
801 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
802 "returned from the widget configuration activity.");
803 result = RESULT_CANCELED;
804 completeTwoStageWidgetDrop(result, appWidgetId);
805 final Runnable onComplete = new Runnable() {
806 @Override
807 public void run() {
808 exitSpringLoadedDragModeDelayed(false, 0, null);
809 }
810 };
811 if (workspaceLocked) {
812 // No need to remove the empty screen if we're mid-binding, as the
813 // the bind will not add the empty screen.
814 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
815 } else {
816 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
817 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
818 }
819 } else {
820 if (!workspaceLocked) {
821 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
822 // When the screen id represents an actual screen (as opposed to a rank)
823 // we make sure that the drop page actually exists.
824 mPendingAddInfo.screenId =
825 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
826 }
827 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
828
829 dropLayout.setDropPending(true);
830 final Runnable onComplete = new Runnable() {
831 @Override
832 public void run() {
833 completeTwoStageWidgetDrop(resultCode, appWidgetId);
834 dropLayout.setDropPending(false);
835 }
836 };
837 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
838 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
839 } else {
840 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
841 mPendingAddInfo);
842 sPendingAddItem = args;
843 }
844 }
845 return;
846 }
847
848 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
849 if (resultCode == RESULT_OK) {
850 // Update the widget view.
851 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
852 pendingAddWidgetId, mPendingAddInfo);
853 if (workspaceLocked) {
854 sPendingAddItem = args;
855 } else {
856 completeAdd(args);
857 }
858 }
859 // Leave the widget in the pending state if the user canceled the configure.
860 return;
861 }
862
863 // The pattern used here is that a user PICKs a specific application,
864 // which, depending on the target, might need to CREATE the actual target.
865
866 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
867 // launch over to the Music app to actually CREATE_SHORTCUT.
868 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
869 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
870 mPendingAddInfo);
871 if (isWorkspaceLocked()) {
872 sPendingAddItem = args;
873 } else {
874 completeAdd(args);
875 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
876 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
877 }
878 } else if (resultCode == RESULT_CANCELED) {
879 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
880 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
881 }
882 mDragLayer.clearAnimatedView();
883 }
884
885 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
886 appWidgetId, ItemInfo info) {
887 PendingAddArguments args = new PendingAddArguments();
888 args.requestCode = requestCode;
889 args.intent = data;
890 args.container = info.container;
891 args.screenId = info.screenId;
892 args.cellX = info.cellX;
893 args.cellY = info.cellY;
894 args.appWidgetId = appWidgetId;
895 return args;
896 }
897
898 /**
899 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
900 *
901 * @param screenId the screen id to check
902 * @return the new screen, or screenId if it exists
903 */
904 private long ensurePendingDropLayoutExists(long screenId) {
905 CellLayout dropLayout =
906 (CellLayout) mWorkspace.getScreenWithId(screenId);
907 if (dropLayout == null) {
908 // it's possible that the add screen was removed because it was
909 // empty and a re-bind occurred
910 mWorkspace.addExtraEmptyScreen();
911 return mWorkspace.commitExtraEmptyScreen();
912 } else {
913 return screenId;
914 }
915 }
916
917 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
918 CellLayout cellLayout =
919 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
920 Runnable onCompleteRunnable = null;
921 int animationType = 0;
922
923 AppWidgetHostView boundWidget = null;
924 if (resultCode == RESULT_OK) {
925 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
926 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
927 mPendingAddWidgetInfo);
928 boundWidget = layout;
929 onCompleteRunnable = new Runnable() {
930 @Override
931 public void run() {
932 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
933 mPendingAddInfo.screenId, layout, null);
934 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
935 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
936 }
937 };
938 } else if (resultCode == RESULT_CANCELED) {
939 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
940 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
941 }
942 if (mDragLayer.getAnimatedView() != null) {
943 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
944 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
945 animationType, boundWidget, true);
946 } else if (onCompleteRunnable != null) {
947 // The animated view may be null in the case of a rotation during widget configuration
948 onCompleteRunnable.run();
949 }
950 }
951
952 @Override
953 protected void onStop() {
954 super.onStop();
955 FirstFrameAnimatorHelper.setIsVisible(false);
956 }
957
958 @Override
959 protected void onStart() {
960 super.onStart();
961 FirstFrameAnimatorHelper.setIsVisible(true);
962 }
963
964 @Override
965 protected void onResume() {
966 long startTime = 0;
967 if (DEBUG_RESUME_TIME) {
968 startTime = System.currentTimeMillis();
969 Log.v(TAG, "Launcher.onResume()");
970 }
971 super.onResume();
972
973 // Restore the previous launcher state
974 if (mOnResumeState == State.WORKSPACE) {
975 showWorkspace(false);
976 } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
977 showAllApps(false, mAppsCustomizeContent.getContentType(), false);
978 }
979 mOnResumeState = State.NONE;
980
981 // Background was set to gradient in onPause(), restore to black if in all apps.
982 setWorkspaceBackground(mState == State.WORKSPACE);
983
984 mPaused = false;
985 if (mRestoring || mOnResumeNeedsLoad) {
986 setWorkspaceLoading(true);
987 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
988 mRestoring = false;
989 mOnResumeNeedsLoad = false;
990 }
991 if (mBindOnResumeCallbacks.size() > 0) {
992 // We might have postponed some bind calls until onResume (see waitUntilResume) --
993 // execute them here
994 long startTimeCallbacks = 0;
995 if (DEBUG_RESUME_TIME) {
996 startTimeCallbacks = System.currentTimeMillis();
997 }
998
999 if (mAppsCustomizeContent != null) {
1000 mAppsCustomizeContent.setBulkBind(true);
1001 }
1002 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1003 mBindOnResumeCallbacks.get(i).run();
1004 }
1005 if (mAppsCustomizeContent != null) {
1006 mAppsCustomizeContent.setBulkBind(false);
1007 }
1008 mBindOnResumeCallbacks.clear();
1009 if (DEBUG_RESUME_TIME) {
1010 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1011 (System.currentTimeMillis() - startTimeCallbacks));
1012 }
1013 }
1014 if (mOnResumeCallbacks.size() > 0) {
1015 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1016 mOnResumeCallbacks.get(i).run();
1017 }
1018 mOnResumeCallbacks.clear();
1019 }
1020
1021 // Reset the pressed state of icons that were locked in the press state while activities
1022 // were launching
1023 if (mWaitingForResume != null) {
1024 // Resets the previous workspace icon press state
1025 mWaitingForResume.setStayPressed(false);
1026 }
1027
1028 // It is possible that widgets can receive updates while launcher is not in the foreground.
1029 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1030 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1031 // orientation.
1032 getWorkspace().reinflateWidgetsIfNecessary();
1033
1034 // Process any items that were added while Launcher was away.
1035 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1036
1037 // Update the voice search button proxy
1038 updateVoiceButtonProxyVisible(false);
1039
1040 // Again, as with the above scenario, it's possible that one or more of the global icons
1041 // were updated in the wrong orientation.
1042 updateGlobalIcons();
1043 if (DEBUG_RESUME_TIME) {
1044 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1045 }
1046
1047 if (mWorkspace.getCustomContentCallbacks() != null) {
1048 // If we are resuming and the custom content is the current page, we call onShow().
1049 // It is also poassible that onShow will instead be called slightly after first layout
1050 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1051 if (mWorkspace.isOnOrMovingToCustomContent()) {
1052 mWorkspace.getCustomContentCallbacks().onShow(true);
1053 }
1054 }
1055 mWorkspace.updateInteractionForState();
1056 mWorkspace.onResume();
1057
1058 PackageInstallerCompat.getInstance(this).onResume();
1059 }
1060
1061 @Override
1062 protected void onPause() {
1063 // Ensure that items added to Launcher are queued until Launcher returns
1064 InstallShortcutReceiver.enableInstallQueue();
1065 PackageInstallerCompat.getInstance(this).onPause();
1066
1067 super.onPause();
1068 mPaused = true;
1069 mDragController.cancelDrag();
1070 mDragController.resetLastGestureUpTime();
1071
1072 // We call onHide() aggressively. The custom content callbacks should be able to
1073 // debounce excess onHide calls.
1074 if (mWorkspace.getCustomContentCallbacks() != null) {
1075 mWorkspace.getCustomContentCallbacks().onHide();
1076 }
1077 }
1078
1079 QSBScroller mQsbScroller = new QSBScroller() {
1080 int scrollY = 0;
1081
1082 @Override
1083 public void setScrollY(int scroll) {
1084 scrollY = scroll;
1085
1086 if (mWorkspace.isOnOrMovingToCustomContent()) {
1087 mSearchDropTargetBar.setTranslationY(- scrollY);
1088 getQsbBar().setTranslationY(-scrollY);
1089 }
1090 }
1091 };
1092
1093 public void resetQSBScroll() {
1094 mSearchDropTargetBar.animate().translationY(0).start();
1095 getQsbBar().animate().translationY(0).start();
1096 }
1097
1098 public interface CustomContentCallbacks {
1099 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1100 // by a onResume or by scrolling otherwise.
1101 public void onShow(boolean fromResume);
1102
1103 // Custom content is completely hidden
1104 public void onHide();
1105
1106 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1107 public void onScrollProgressChanged(float progress);
1108
1109 // Indicates whether the user is allowed to scroll away from the custom content.
1110 boolean isScrollingAllowed();
1111 }
1112
1113 protected boolean hasSettings() {
1114 return false;
1115 }
1116
1117 public interface QSBScroller {
1118 public void setScrollY(int scrollY);
1119 }
1120
1121 public QSBScroller addToCustomContentPage(View customContent,
1122 CustomContentCallbacks callbacks, String description) {
1123 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1124 return mQsbScroller;
1125 }
1126
1127 // The custom content needs to offset its content to account for the QSB
1128 public int getTopOffsetForCustomContent() {
1129 return mWorkspace.getPaddingTop();
1130 }
1131
1132 @Override
1133 public Object onRetainNonConfigurationInstance() {
1134 // Flag the loader to stop early before switching
1135 if (mModel.isCurrentCallbacks(this)) {
1136 mModel.stopLoader();
1137 }
1138 if (mAppsCustomizeContent != null) {
1139 mAppsCustomizeContent.surrender();
1140 }
1141 return Boolean.TRUE;
1142 }
1143
1144 // We can't hide the IME if it was forced open. So don't bother
1145 @Override
1146 public void onWindowFocusChanged(boolean hasFocus) {
1147 super.onWindowFocusChanged(hasFocus);
1148 mHasFocus = hasFocus;
1149 }
1150
1151 private boolean acceptFilter() {
1152 final InputMethodManager inputManager = (InputMethodManager)
1153 getSystemService(Context.INPUT_METHOD_SERVICE);
1154 return !inputManager.isFullscreenMode();
1155 }
1156
1157 @Override
1158 public boolean onKeyDown(int keyCode, KeyEvent event) {
1159 final int uniChar = event.getUnicodeChar();
1160 final boolean handled = super.onKeyDown(keyCode, event);
1161 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1162 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1163 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1164 keyCode, event);
1165 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1166 // something usable has been typed - start a search
1167 // the typed text will be retrieved and cleared by
1168 // showSearchDialog()
1169 // If there are multiple keystrokes before the search dialog takes focus,
1170 // onSearchRequested() will be called for every keystroke,
1171 // but it is idempotent, so it's fine.
1172 return onSearchRequested();
1173 }
1174 }
1175
1176 // Eat the long press event so the keyboard doesn't come up.
1177 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1178 return true;
1179 }
1180
1181 return handled;
1182 }
1183
1184 private String getTypedText() {
1185 return mDefaultKeySsb.toString();
1186 }
1187
1188 private void clearTypedText() {
1189 mDefaultKeySsb.clear();
1190 mDefaultKeySsb.clearSpans();
1191 Selection.setSelection(mDefaultKeySsb, 0);
1192 }
1193
1194 /**
1195 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1196 * State
1197 */
1198 private static State intToState(int stateOrdinal) {
1199 State state = State.WORKSPACE;
1200 final State[] stateValues = State.values();
1201 for (int i = 0; i < stateValues.length; i++) {
1202 if (stateValues[i].ordinal() == stateOrdinal) {
1203 state = stateValues[i];
1204 break;
1205 }
1206 }
1207 return state;
1208 }
1209
1210 /**
1211 * Restores the previous state, if it exists.
1212 *
1213 * @param savedState The previous state.
1214 */
1215 @SuppressWarnings("unchecked")
1216 private void restoreState(Bundle savedState) {
1217 if (savedState == null) {
1218 return;
1219 }
1220
1221 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1222 if (state == State.APPS_CUSTOMIZE) {
1223 mOnResumeState = State.APPS_CUSTOMIZE;
1224 }
1225
1226 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1227 PagedView.INVALID_RESTORE_PAGE);
1228 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1229 mWorkspace.setRestorePage(currentScreen);
1230 }
1231
1232 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1233 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1234
1235 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1236 mPendingAddInfo.container = pendingAddContainer;
1237 mPendingAddInfo.screenId = pendingAddScreen;
1238 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1239 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1240 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1241 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1242 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1243 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1244 setWaitingForResult(true);
1245 mRestoring = true;
1246 }
1247
1248 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1249 if (renameFolder) {
1250 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1251 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1252 mRestoring = true;
1253 }
1254
1255 // Restore the AppsCustomize tab
1256 if (mAppsCustomizeTabHost != null) {
1257 String curTab = savedState.getString("apps_customize_currentTab");
1258 if (curTab != null) {
1259 mAppsCustomizeTabHost.setContentTypeImmediate(
1260 mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
1261 mAppsCustomizeContent.loadAssociatedPages(
1262 mAppsCustomizeContent.getCurrentPage());
1263 }
1264
1265 int currentIndex = savedState.getInt("apps_customize_currentIndex");
1266 mAppsCustomizeContent.restorePageForIndex(currentIndex);
1267 }
1268 mItemIdToViewId = (HashMap<Integer, Integer>)
1269 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1270 }
1271
1272 /**
1273 * Finds all the views we need and configure them properly.
1274 */
1275 private void setupViews() {
1276 final DragController dragController = mDragController;
1277
1278 mLauncherView = findViewById(R.id.launcher);
1279 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1280 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1281 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1282 mWorkspace.setPageSwitchListener(this);
1283 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1284
1285 mLauncherView.setSystemUiVisibility(
1286 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1287 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1288
1289 // Setup the drag layer
1290 mDragLayer.setup(this, dragController);
1291
1292 // Setup the hotseat
1293 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1294 if (mHotseat != null) {
1295 mHotseat.setup(this);
1296 mHotseat.setOnLongClickListener(this);
1297 }
1298
1299 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1300 View widgetButton = findViewById(R.id.widget_button);
1301 widgetButton.setOnClickListener(new OnClickListener() {
1302 @Override
1303 public void onClick(View arg0) {
1304 if (!mWorkspace.isSwitchingState()) {
1305 onClickAddWidgetButton(arg0);
1306 }
1307 }
1308 });
1309 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1310
1311 View wallpaperButton = findViewById(R.id.wallpaper_button);
1312 wallpaperButton.setOnClickListener(new OnClickListener() {
1313 @Override
1314 public void onClick(View arg0) {
1315 if (!mWorkspace.isSwitchingState()) {
1316 onClickWallpaperPicker(arg0);
1317 }
1318 }
1319 });
1320 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1321
1322 View settingsButton = findViewById(R.id.settings_button);
1323 if (hasSettings()) {
1324 settingsButton.setOnClickListener(new OnClickListener() {
1325 @Override
1326 public void onClick(View arg0) {
1327 if (!mWorkspace.isSwitchingState()) {
1328 onClickSettingsButton(arg0);
1329 }
1330 }
1331 });
1332 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1333 } else {
1334 settingsButton.setVisibility(View.GONE);
1335 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams();
1336 lp.gravity = Gravity.END | Gravity.TOP;
1337 widgetButton.requestLayout();
1338 }
1339
1340 mOverviewPanel.setAlpha(0f);
1341
1342 // Setup the workspace
1343 mWorkspace.setHapticFeedbackEnabled(false);
1344 mWorkspace.setOnLongClickListener(this);
1345 mWorkspace.setup(dragController);
1346 dragController.addDragListener(mWorkspace);
1347
1348 // Get the search/delete bar
1349 mSearchDropTargetBar = (SearchDropTargetBar)
1350 mDragLayer.findViewById(R.id.search_drop_target_bar);
1351
1352 // Setup AppsCustomize
1353 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
1354 mAppsCustomizeContent = (AppsCustomizePagedView)
1355 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
1356 mAppsCustomizeContent.setup(this, dragController);
1357
1358 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1359 dragController.setDragScoller(mWorkspace);
1360 dragController.setScrollView(mDragLayer);
1361 dragController.setMoveTarget(mWorkspace);
1362 dragController.addDropTarget(mWorkspace);
1363 if (mSearchDropTargetBar != null) {
1364 mSearchDropTargetBar.setup(this, dragController);
1365 }
1366
1367 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1368 Log.v(TAG, "adding WeightWatcher");
1369 mWeightWatcher = new WeightWatcher(this);
1370 mWeightWatcher.setAlpha(0.5f);
1371 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1372 new FrameLayout.LayoutParams(
1373 FrameLayout.LayoutParams.MATCH_PARENT,
1374 FrameLayout.LayoutParams.WRAP_CONTENT,
1375 Gravity.BOTTOM)
1376 );
1377
1378 boolean show = shouldShowWeightWatcher();
1379 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1380 }
1381 }
1382
1383 /**
1384 * Sets the all apps button. This method is called from {@link Hotseat}.
1385 */
1386 public void setAllAppsButton(View allAppsButton) {
1387 mAllAppsButton = allAppsButton;
1388 }
1389
1390 public View getAllAppsButton() {
1391 return mAllAppsButton;
1392 }
1393
1394 /**
1395 * Creates a view representing a shortcut.
1396 *
1397 * @param info The data structure describing the shortcut.
1398 *
1399 * @return A View inflated from R.layout.application.
1400 */
1401 View createShortcut(ShortcutInfo info) {
1402 return createShortcut(R.layout.application,
1403 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1404 }
1405
1406 /**
1407 * Creates a view representing a shortcut inflated from the specified resource.
1408 *
1409 * @param layoutResId The id of the XML layout used to create the shortcut.
1410 * @param parent The group the shortcut belongs to.
1411 * @param info The data structure describing the shortcut.
1412 *
1413 * @return A View inflated from layoutResId.
1414 */
1415 View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1416 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1417 favorite.applyFromShortcutInfo(info, mIconCache, true);
1418 favorite.setOnClickListener(this);
1419 favorite.setOnFocusChangeListener(mFocusHandler);
1420 return favorite;
1421 }
1422
1423 /**
1424 * Add a shortcut to the workspace.
1425 *
1426 * @param data The intent describing the shortcut.
1427 * @param cellInfo The position on screen where to create the shortcut.
1428 */
1429 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1430 int cellY) {
1431 int[] cellXY = mTmpAddItemCellCoordinates;
1432 int[] touchXY = mPendingAddInfo.dropPos;
1433 CellLayout layout = getCellLayout(container, screenId);
1434
1435 boolean foundCellSpan = false;
1436
1437 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
1438 if (info == null) {
1439 return;
1440 }
1441 final View view = createShortcut(info);
1442
1443 // First we check if we already know the exact location where we want to add this item.
1444 if (cellX >= 0 && cellY >= 0) {
1445 cellXY[0] = cellX;
1446 cellXY[1] = cellY;
1447 foundCellSpan = true;
1448
1449 // If appropriate, either create a folder or add to an existing folder
1450 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1451 true, null,null)) {
1452 return;
1453 }
1454 DragObject dragObject = new DragObject();
1455 dragObject.dragInfo = info;
1456 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1457 true)) {
1458 return;
1459 }
1460 } else if (touchXY != null) {
1461 // when dragging and dropping, just find the closest free spot
1462 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1463 foundCellSpan = (result != null);
1464 } else {
1465 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1466 }
1467
1468 if (!foundCellSpan) {
1469 showOutOfSpaceMessage(isHotseatLayout(layout));
1470 return;
1471 }
1472
1473 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
1474
1475 if (!mRestoring) {
1476 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1477 isWorkspaceLocked());
1478 }
1479 }
1480
1481 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1482 int minHeight) {
1483 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1484 // We want to account for the extra amount of padding that we are adding to the widget
1485 // to ensure that it gets the full amount of space that it has requested
1486 int requiredWidth = minWidth + padding.left + padding.right;
1487 int requiredHeight = minHeight + padding.top + padding.bottom;
1488 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1489 }
1490
1491 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1492 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1493 }
1494
1495 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1496 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1497 }
1498
1499 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1500 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1501 }
1502
1503 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1504 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1505 info.minResizeHeight);
1506 }
1507
1508 /**
1509 * Add a widget to the workspace.
1510 *
1511 * @param appWidgetId The app widget id
1512 * @param cellInfo The position on screen where to create the widget.
1513 */
1514 private void completeAddAppWidget(final int appWidgetId, long container, long screenId,
1515 AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
1516 if (appWidgetInfo == null) {
1517 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1518 }
1519
1520 // Calculate the grid spans needed to fit this widget
1521 CellLayout layout = getCellLayout(container, screenId);
1522
1523 int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
1524 int[] spanXY = getSpanForWidget(this, appWidgetInfo);
1525
1526 // Try finding open space on Launcher screen
1527 // We have saved the position to which the widget was dragged-- this really only matters
1528 // if we are placing widgets on a "spring-loaded" screen
1529 int[] cellXY = mTmpAddItemCellCoordinates;
1530 int[] touchXY = mPendingAddInfo.dropPos;
1531 int[] finalSpan = new int[2];
1532 boolean foundCellSpan = false;
1533 if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
1534 cellXY[0] = mPendingAddInfo.cellX;
1535 cellXY[1] = mPendingAddInfo.cellY;
1536 spanXY[0] = mPendingAddInfo.spanX;
1537 spanXY[1] = mPendingAddInfo.spanY;
1538 foundCellSpan = true;
1539 } else if (touchXY != null) {
1540 // when dragging and dropping, just find the closest free spot
1541 int[] result = layout.findNearestVacantArea(
1542 touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
1543 spanXY[1], cellXY, finalSpan);
1544 spanXY[0] = finalSpan[0];
1545 spanXY[1] = finalSpan[1];
1546 foundCellSpan = (result != null);
1547 } else {
1548 foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
1549 }
1550
1551 if (!foundCellSpan) {
1552 if (appWidgetId != -1) {
1553 // Deleting an app widget ID is a void call but writes to disk before returning
1554 // to the caller...
1555 new AsyncTask<Void, Void, Void>() {
1556 public Void doInBackground(Void ... args) {
1557 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1558 return null;
1559 }
1560 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1561 }
1562 showOutOfSpaceMessage(isHotseatLayout(layout));
1563 return;
1564 }
1565
1566 // Build Launcher-specific widget info and save to database
1567 LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
1568 appWidgetInfo.provider);
1569 launcherInfo.spanX = spanXY[0];
1570 launcherInfo.spanY = spanXY[1];
1571 launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
1572 launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
1573 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1574
1575 LauncherModel.addItemToDatabase(this, launcherInfo,
1576 container, screenId, cellXY[0], cellXY[1], false);
1577
1578 if (!mRestoring) {
1579 if (hostView == null) {
1580 // Perform actual inflation because we're live
1581 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1582 launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
1583 } else {
1584 // The AppWidgetHostView has already been inflated and instantiated
1585 launcherInfo.hostView = hostView;
1586 }
1587
1588 launcherInfo.hostView.setTag(launcherInfo);
1589 launcherInfo.hostView.setVisibility(View.VISIBLE);
1590 launcherInfo.notifyWidgetSizeChanged(this);
1591
1592 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, cellXY[0], cellXY[1],
1593 launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1594
1595 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1596 }
1597 resetAddInfo();
1598 }
1599
1600 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1601 @Override
1602 public void onReceive(Context context, Intent intent) {
1603 final String action = intent.getAction();
1604 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1605 mUserPresent = false;
1606 mDragLayer.clearAllResizeFrames();
1607 updateRunning();
1608
1609 // Reset AllApps to its initial state only if we are not in the middle of
1610 // processing a multi-step drop
1611 if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
1612 showWorkspace(false);
1613 }
1614 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1615 mUserPresent = true;
1616 updateRunning();
1617 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1618 mModel.resetLoadedState(false, true);
1619 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1620 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1621 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1622 mModel.resetLoadedState(false, true);
1623 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1624 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1625 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1626 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1627 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1628 getModel().forceReload();
1629 }
1630 }
1631 };
1632
1633 @Override
1634 public void onAttachedToWindow() {
1635 super.onAttachedToWindow();
1636
1637 // Listen for broadcasts related to user-presence
1638 final IntentFilter filter = new IntentFilter();
1639 filter.addAction(Intent.ACTION_SCREEN_OFF);
1640 filter.addAction(Intent.ACTION_USER_PRESENT);
1641 // For handling managed profiles
1642 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1643 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1644 if (ENABLE_DEBUG_INTENTS) {
1645 filter.addAction(DebugIntents.DELETE_DATABASE);
1646 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1647 }
1648 registerReceiver(mReceiver, filter);
1649 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1650 setupTransparentSystemBarsForLmp();
1651 mAttached = true;
1652 mVisible = true;
1653 }
1654
1655 /**
1656 * Sets up transparent navigation and status bars in LMP.
1657 * This method is a no-op for other platform versions.
1658 */
1659 @TargetApi(19)
1660 private void setupTransparentSystemBarsForLmp() {
1661 // TODO(sansid): use the APIs directly when compiling against L sdk.
1662 // Currently we use reflection to access the flags and the API to set the transparency
1663 // on the System bars.
1664 if (Utilities.isLmpOrAbove()) {
1665 try {
1666 getWindow().getAttributes().systemUiVisibility |=
1667 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1668 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1669 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1670 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1671 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1672 Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField(
1673 "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS");
1674 getWindow().addFlags(drawsSysBackgroundsField.getInt(null));
1675
1676 Method setStatusBarColorMethod =
1677 Window.class.getDeclaredMethod("setStatusBarColor", int.class);
1678 Method setNavigationBarColorMethod =
1679 Window.class.getDeclaredMethod("setNavigationBarColor", int.class);
1680 setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
1681 setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
1682 } catch (NoSuchFieldException e) {
1683 Log.w(TAG, "NoSuchFieldException while setting up transparent bars");
1684 } catch (NoSuchMethodException ex) {
1685 Log.w(TAG, "NoSuchMethodException while setting up transparent bars");
1686 } catch (IllegalAccessException e) {
1687 Log.w(TAG, "IllegalAccessException while setting up transparent bars");
1688 } catch (IllegalArgumentException e) {
1689 Log.w(TAG, "IllegalArgumentException while setting up transparent bars");
1690 } catch (InvocationTargetException e) {
1691 Log.w(TAG, "InvocationTargetException while setting up transparent bars");
1692 } finally {}
1693 }
1694 }
1695
1696 @Override
1697 public void onDetachedFromWindow() {
1698 super.onDetachedFromWindow();
1699 mVisible = false;
1700
1701 if (mAttached) {
1702 unregisterReceiver(mReceiver);
1703 mAttached = false;
1704 }
1705 updateRunning();
1706 }
1707
1708 public void onWindowVisibilityChanged(int visibility) {
1709 mVisible = visibility == View.VISIBLE;
1710 updateRunning();
1711 // The following code used to be in onResume, but it turns out onResume is called when
1712 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1713 // is a more appropriate event to handle
1714 if (mVisible) {
1715 mAppsCustomizeTabHost.onWindowVisible();
1716 if (!mWorkspaceLoading) {
1717 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1718 // We want to let Launcher draw itself at least once before we force it to build
1719 // layers on all the workspace pages, so that transitioning to Launcher from other
1720 // apps is nice and speedy.
1721 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1722 private boolean mStarted = false;
1723 public void onDraw() {
1724 if (mStarted) return;
1725 mStarted = true;
1726 // We delay the layer building a bit in order to give
1727 // other message processing a time to run. In particular
1728 // this avoids a delay in hiding the IME if it was
1729 // currently shown, because doing that may involve
1730 // some communication back with the app.
1731 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1732 final ViewTreeObserver.OnDrawListener listener = this;
1733 mWorkspace.post(new Runnable() {
1734 public void run() {
1735 if (mWorkspace != null &&
1736 mWorkspace.getViewTreeObserver() != null) {
1737 mWorkspace.getViewTreeObserver().
1738 removeOnDrawListener(listener);
1739 }
1740 }
1741 });
1742 return;
1743 }
1744 });
1745 }
1746 clearTypedText();
1747 }
1748 }
1749
1750 private void sendAdvanceMessage(long delay) {
1751 mHandler.removeMessages(ADVANCE_MSG);
1752 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1753 mHandler.sendMessageDelayed(msg, delay);
1754 mAutoAdvanceSentTime = System.currentTimeMillis();
1755 }
1756
1757 private void updateRunning() {
1758 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1759 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1760 mAutoAdvanceRunning = autoAdvanceRunning;
1761 if (autoAdvanceRunning) {
1762 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1763 sendAdvanceMessage(delay);
1764 } else {
1765 if (!mWidgetsToAdvance.isEmpty()) {
1766 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1767 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1768 }
1769 mHandler.removeMessages(ADVANCE_MSG);
1770 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1771 }
1772 }
1773 }
1774
1775 private final Handler mHandler = new Handler() {
1776 @Override
1777 public void handleMessage(Message msg) {
1778 if (msg.what == ADVANCE_MSG) {
1779 int i = 0;
1780 for (View key: mWidgetsToAdvance.keySet()) {
1781 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1782 final int delay = mAdvanceStagger * i;
1783 if (v instanceof Advanceable) {
1784 postDelayed(new Runnable() {
1785 public void run() {
1786 ((Advanceable) v).advance();
1787 }
1788 }, delay);
1789 }
1790 i++;
1791 }
1792 sendAdvanceMessage(mAdvanceInterval);
1793 }
1794 }
1795 };
1796
1797 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1798 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1799 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1800 if (v instanceof Advanceable) {
1801 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1802 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1803 updateRunning();
1804 }
1805 }
1806
1807 void removeWidgetToAutoAdvance(View hostView) {
1808 if (mWidgetsToAdvance.containsKey(hostView)) {
1809 mWidgetsToAdvance.remove(hostView);
1810 updateRunning();
1811 }
1812 }
1813
1814 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1815 removeWidgetToAutoAdvance(launcherInfo.hostView);
1816 launcherInfo.hostView = null;
1817 }
1818
1819 void showOutOfSpaceMessage(boolean isHotseatLayout) {
1820 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1821 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1822 }
1823
1824 public DragLayer getDragLayer() {
1825 return mDragLayer;
1826 }
1827
1828 public Workspace getWorkspace() {
1829 return mWorkspace;
1830 }
1831
1832 public Hotseat getHotseat() {
1833 return mHotseat;
1834 }
1835
1836 public ViewGroup getOverviewPanel() {
1837 return mOverviewPanel;
1838 }
1839
1840 public SearchDropTargetBar getSearchBar() {
1841 return mSearchDropTargetBar;
1842 }
1843
1844 public LauncherAppWidgetHost getAppWidgetHost() {
1845 return mAppWidgetHost;
1846 }
1847
1848 public LauncherModel getModel() {
1849 return mModel;
1850 }
1851
1852 protected SharedPreferences getSharedPrefs() {
1853 return mSharedPrefs;
1854 }
1855
1856 public void closeSystemDialogs() {
1857 getWindow().closeAllPanels();
1858
1859 // Whatever we were doing is hereby canceled.
1860 setWaitingForResult(false);
1861 }
1862
1863 @Override
1864 protected void onNewIntent(Intent intent) {
1865 long startTime = 0;
1866 if (DEBUG_RESUME_TIME) {
1867 startTime = System.currentTimeMillis();
1868 }
1869 super.onNewIntent(intent);
1870
1871 // Close the menu
1872 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1873 // also will cancel mWaitingForResult.
1874 closeSystemDialogs();
1875
1876 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1877 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1878 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1879
1880 if (mWorkspace == null) {
1881 // Can be cases where mWorkspace is null, this prevents a NPE
1882 return;
1883 }
1884 Folder openFolder = mWorkspace.getOpenFolder();
1885 // In all these cases, only animate if we're already on home
1886 mWorkspace.exitWidgetResizeMode();
1887 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1888 openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) {
1889 mWorkspace.moveToDefaultScreen(true);
1890 }
1891
1892 closeFolder();
1893 exitSpringLoadedDragMode();
1894
1895 // If we are already on home, then just animate back to the workspace,
1896 // otherwise, just wait until onResume to set the state back to Workspace
1897 if (alreadyOnHome) {
1898 showWorkspace(true);
1899 } else {
1900 mOnResumeState = State.WORKSPACE;
1901 }
1902
1903 final View v = getWindow().peekDecorView();
1904 if (v != null && v.getWindowToken() != null) {
1905 InputMethodManager imm = (InputMethodManager)getSystemService(
1906 INPUT_METHOD_SERVICE);
1907 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1908 }
1909
1910 // Reset the apps customize page
1911 if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
1912 mAppsCustomizeTabHost.reset();
1913 }
1914
1915 onHomeIntent();
1916 }
1917
1918 if (DEBUG_RESUME_TIME) {
1919 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1920 }
1921 }
1922
1923 /**
1924 * Override point for subclasses to prevent movement to the default screen when the home
1925 * button is pressed. Used (for example) in GEL, to prevent movement during a search.
1926 */
1927 protected boolean shouldMoveToDefaultScreenOnHomeIntent() {
1928 return true;
1929 }
1930
1931 /**
1932 * Override point for subclasses to provide custom behaviour for when a home intent is fired.
1933 */
1934 protected void onHomeIntent() {
1935 // Do nothing
1936 }
1937
1938 @Override
1939 public void onRestoreInstanceState(Bundle state) {
1940 super.onRestoreInstanceState(state);
1941 for (int page: mSynchronouslyBoundPages) {
1942 mWorkspace.restoreInstanceStateForChild(page);
1943 }
1944 }
1945
1946 @Override
1947 protected void onSaveInstanceState(Bundle outState) {
1948 if (mWorkspace.getChildCount() > 0) {
1949 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1950 mWorkspace.getCurrentPageOffsetFromCustomContent());
1951 }
1952 super.onSaveInstanceState(outState);
1953
1954 outState.putInt(RUNTIME_STATE, mState.ordinal());
1955 // We close any open folder since it will not be re-opened, and we need to make sure
1956 // this state is reflected.
1957 closeFolder();
1958
1959 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
1960 mWaitingForResult) {
1961 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1962 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1963 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1964 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1965 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1966 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1967 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1968 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1969 }
1970
1971 if (mFolderInfo != null && mWaitingForResult) {
1972 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1973 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1974 }
1975
1976 // Save the current AppsCustomize tab
1977 if (mAppsCustomizeTabHost != null) {
1978 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
1979 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
1980 if (currentTabTag != null) {
1981 outState.putString("apps_customize_currentTab", currentTabTag);
1982 }
1983 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
1984 outState.putInt("apps_customize_currentIndex", currentIndex);
1985 }
1986 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
1987 }
1988
1989 @Override
1990 public void onDestroy() {
1991 super.onDestroy();
1992
1993 // Remove all pending runnables
1994 mHandler.removeMessages(ADVANCE_MSG);
1995 mHandler.removeMessages(0);
1996 mWorkspace.removeCallbacks(mBuildLayersRunnable);
1997
1998 // Stop callbacks from LauncherModel
1999 LauncherAppState app = (LauncherAppState.getInstance());
2000
2001 // It's possible to receive onDestroy after a new Launcher activity has
2002 // been created. In this case, don't interfere with the new Launcher.
2003 if (mModel.isCurrentCallbacks(this)) {
2004 mModel.stopLoader();
2005 app.setLauncher(null);
2006 }
2007
2008 try {
2009 mAppWidgetHost.stopListening();
2010 } catch (NullPointerException ex) {
2011 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2012 }
2013 mAppWidgetHost = null;
2014
2015 mWidgetsToAdvance.clear();
2016
2017 TextKeyListener.getInstance().release();
2018
2019 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2020 // to prevent leaking Launcher activities on orientation change.
2021 if (mModel != null) {
2022 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2023 }
2024
2025 getContentResolver().unregisterContentObserver(mWidgetObserver);
2026 unregisterReceiver(mCloseSystemDialogsReceiver);
2027
2028 mDragLayer.clearAllResizeFrames();
2029 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2030 mWorkspace.removeAllWorkspaceScreens();
2031 mWorkspace = null;
2032 mDragController = null;
2033
2034 PackageInstallerCompat.getInstance(this).onStop();
2035 LauncherAnimUtils.onDestroyActivity();
2036 }
2037
2038 public DragController getDragController() {
2039 return mDragController;
2040 }
2041
2042 @Override
2043 public void startActivityForResult(Intent intent, int requestCode) {
2044 if (requestCode >= 0) {
2045 setWaitingForResult(true);
2046 }
2047 super.startActivityForResult(intent, requestCode);
2048 }
2049
2050 /**
2051 * Indicates that we want global search for this activity by setting the globalSearch
2052 * argument for {@link #startSearch} to true.
2053 */
2054 @Override
2055 public void startSearch(String initialQuery, boolean selectInitialQuery,
2056 Bundle appSearchData, boolean globalSearch) {
2057
2058 showWorkspace(true);
2059
2060 if (initialQuery == null) {
2061 // Use any text typed in the launcher as the initial query
2062 initialQuery = getTypedText();
2063 }
2064 if (appSearchData == null) {
2065 appSearchData = new Bundle();
2066 appSearchData.putString("source", "launcher-search");
2067 }
2068 Rect sourceBounds = new Rect();
2069 if (mSearchDropTargetBar != null) {
2070 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2071 }
2072
2073 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2074 appSearchData, sourceBounds);
2075 if (clearTextImmediately) {
2076 clearTypedText();
2077 }
2078 }
2079
2080 /**
2081 * Start a text search.
2082 *
2083 * @return {@code true} if the search will start immediately, so any further keypresses
2084 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2085 * to buffer keypresses.
2086 */
2087 public boolean startSearch(String initialQuery,
2088 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2089 startGlobalSearch(initialQuery, selectInitialQuery,
2090 appSearchData, sourceBounds);
2091 return false;
2092 }
2093
2094 /**
2095 * Starts the global search activity. This code is a copied from SearchManager
2096 */
2097 private void startGlobalSearch(String initialQuery,
2098 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2099 final SearchManager searchManager =
2100 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2101 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2102 if (globalSearchActivity == null) {
2103 Log.w(TAG, "No global search activity found.");
2104 return;
2105 }
2106 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2107 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2108 intent.setComponent(globalSearchActivity);
2109 // Make sure that we have a Bundle to put source in
2110 if (appSearchData == null) {
2111 appSearchData = new Bundle();
2112 } else {
2113 appSearchData = new Bundle(appSearchData);
2114 }
2115 // Set source to package name of app that starts global search, if not set already.
2116 if (!appSearchData.containsKey("source")) {
2117 appSearchData.putString("source", getPackageName());
2118 }
2119 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2120 if (!TextUtils.isEmpty(initialQuery)) {
2121 intent.putExtra(SearchManager.QUERY, initialQuery);
2122 }
2123 if (selectInitialQuery) {
2124 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2125 }
2126 intent.setSourceBounds(sourceBounds);
2127 try {
2128 startActivity(intent);
2129 } catch (ActivityNotFoundException ex) {
2130 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2131 }
2132 }
2133
2134 public boolean isOnCustomContent() {
2135 return mWorkspace.isOnOrMovingToCustomContent();
2136 }
2137
2138 @Override
2139 public boolean onPrepareOptionsMenu(Menu menu) {
2140 super.onPrepareOptionsMenu(menu);
2141 if (!isOnCustomContent()) {
2142 // Close any open folders
2143 closeFolder();
2144 // Stop resizing any widgets
2145 mWorkspace.exitWidgetResizeMode();
2146 if (!mWorkspace.isInOverviewMode()) {
2147 // Show the overview mode
2148 showOverviewMode(true);
2149 } else {
2150 showWorkspace(true);
2151 }
2152 }
2153 return false;
2154 }
2155
2156 @Override
2157 public boolean onSearchRequested() {
2158 startSearch(null, false, null, true);
2159 // Use a custom animation for launching search
2160 return true;
2161 }
2162
2163 public boolean isWorkspaceLocked() {
2164 return mWorkspaceLoading || mWaitingForResult;
2165 }
2166
2167 public boolean isWorkspaceLoading() {
2168 return mWorkspaceLoading;
2169 }
2170
2171 private void setWorkspaceLoading(boolean value) {
2172 boolean isLocked = isWorkspaceLocked();
2173 mWorkspaceLoading = value;
2174 if (isLocked != isWorkspaceLocked()) {
2175 onWorkspaceLockedChanged();
2176 }
2177 }
2178
2179 private void setWaitingForResult(boolean value) {
2180 boolean isLocked = isWorkspaceLocked();
2181 mWaitingForResult = value;
2182 if (isLocked != isWorkspaceLocked()) {
2183 onWorkspaceLockedChanged();
2184 }
2185 }
2186
2187 protected void onWorkspaceLockedChanged() { }
2188
2189 private void resetAddInfo() {
2190 mPendingAddInfo.container = ItemInfo.NO_ID;
2191 mPendingAddInfo.screenId = -1;
2192 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2193 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2194 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2195 mPendingAddInfo.dropPos = null;
2196 }
2197
2198 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2199 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo) {
2200 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2201 }
2202
2203 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2204 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo, int
2205 delay) {
2206 if (appWidgetInfo.configure != null) {
2207 mPendingAddWidgetInfo = appWidgetInfo;
2208 mPendingAddWidgetId = appWidgetId;
2209
2210 // Launch over to configure widget, if needed
2211 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2212 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2213
2214 } else {
2215 // Otherwise just add it
2216 Runnable onComplete = new Runnable() {
2217 @Override
2218 public void run() {
2219 // Exit spring loaded mode if necessary after adding the widget
2220 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2221 null);
2222 }
2223 };
2224 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2225 appWidgetInfo);
2226 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2227 }
2228 }
2229
2230 protected void moveToCustomContentScreen(boolean animate) {
2231 // Close any folders that may be open.
2232 closeFolder();
2233 mWorkspace.moveToCustomContentScreen(animate);
2234 }
2235 /**
2236 * Process a shortcut drop.
2237 *
2238 * @param componentName The name of the component
2239 * @param screenId The ID of the screen where it should be added
2240 * @param cell The cell it should be added to, optional
2241 * @param position The location on the screen where it was dropped, optional
2242 */
2243 void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2244 int[] cell, int[] loc) {
2245 resetAddInfo();
2246 mPendingAddInfo.container = container;
2247 mPendingAddInfo.screenId = screenId;
2248 mPendingAddInfo.dropPos = loc;
2249
2250 if (cell != null) {
2251 mPendingAddInfo.cellX = cell[0];
2252 mPendingAddInfo.cellY = cell[1];
2253 }
2254
2255 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2256 createShortcutIntent.setComponent(componentName);
2257 processShortcut(createShortcutIntent);
2258 }
2259
2260 /**
2261 * Process a widget drop.
2262 *
2263 * @param info The PendingAppWidgetInfo of the widget being added.
2264 * @param screenId The ID of the screen where it should be added
2265 * @param cell The cell it should be added to, optional
2266 * @param position The location on the screen where it was dropped, optional
2267 */
2268 void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2269 int[] cell, int[] span, int[] loc) {
2270 resetAddInfo();
2271 mPendingAddInfo.container = info.container = container;
2272 mPendingAddInfo.screenId = info.screenId = screenId;
2273 mPendingAddInfo.dropPos = loc;
2274 mPendingAddInfo.minSpanX = info.minSpanX;
2275 mPendingAddInfo.minSpanY = info.minSpanY;
2276
2277 if (cell != null) {
2278 mPendingAddInfo.cellX = cell[0];
2279 mPendingAddInfo.cellY = cell[1];
2280 }
2281 if (span != null) {
2282 mPendingAddInfo.spanX = span[0];
2283 mPendingAddInfo.spanY = span[1];
2284 }
2285
2286 AppWidgetHostView hostView = info.boundWidget;
2287 int appWidgetId;
2288 if (hostView != null) {
2289 appWidgetId = hostView.getAppWidgetId();
2290 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2291 } else {
2292 // In this case, we either need to start an activity to get permission to bind
2293 // the widget, or we need to start an activity to configure the widget, or both.
2294 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2295 Bundle options = info.bindOptions;
2296
2297 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2298 appWidgetId, info.info, options);
2299 if (success) {
2300 addAppWidgetImpl(appWidgetId, info, null, info.info);
2301 } else {
2302 mPendingAddWidgetInfo = info.info;
2303 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2304 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2305 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2306 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2307 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2308 // TODO: we need to make sure that this accounts for the options bundle.
2309 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2310 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2311 }
2312 }
2313 }
2314
2315 void processShortcut(Intent intent) {
2316 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2317 }
2318
2319 void processWallpaper(Intent intent) {
2320 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2321 }
2322
2323 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2324 int cellY) {
2325 final FolderInfo folderInfo = new FolderInfo();
2326 folderInfo.title = getText(R.string.folder_name);
2327
2328 // Update the model
2329 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
2330 false);
2331 sFolders.put(folderInfo.id, folderInfo);
2332
2333 // Create the view
2334 FolderIcon newFolder =
2335 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2336 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2337 isWorkspaceLocked());
2338 // Force measure the new folder icon
2339 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2340 parent.getShortcutsAndWidgets().measureChild(newFolder);
2341 return newFolder;
2342 }
2343
2344 void removeFolder(FolderInfo folder) {
2345 sFolders.remove(folder.id);
2346 }
2347
2348 protected ComponentName getWallpaperPickerComponent() {
2349 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
2350 }
2351
2352 /**
2353 * Registers various content observers. The current implementation registers
2354 * only a favorites observer to keep track of the favorites applications.
2355 */
2356 private void registerContentObservers() {
2357 ContentResolver resolver = getContentResolver();
2358 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2359 true, mWidgetObserver);
2360 }
2361
2362 @Override
2363 public boolean dispatchKeyEvent(KeyEvent event) {
2364 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2365 switch (event.getKeyCode()) {
2366 case KeyEvent.KEYCODE_HOME:
2367 return true;
2368 case KeyEvent.KEYCODE_VOLUME_DOWN:
2369 if (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2370 dumpState();
2371 return true;
2372 }
2373 break;
2374 }
2375 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2376 switch (event.getKeyCode()) {
2377 case KeyEvent.KEYCODE_HOME:
2378 return true;
2379 }
2380 }
2381
2382 return super.dispatchKeyEvent(event);
2383 }
2384
2385 @Override
2386 public void onBackPressed() {
2387 if (isAllAppsVisible()) {
2388 if (mAppsCustomizeContent.getContentType() ==
2389 AppsCustomizePagedView.ContentType.Applications) {
2390 showWorkspace(true);
2391 } else {
2392 showOverviewMode(true);
2393 }
2394 } else if (mWorkspace.isInOverviewMode()) {
2395 mWorkspace.exitOverviewMode(true);
2396 } else if (mWorkspace.getOpenFolder() != null) {
2397 Folder openFolder = mWorkspace.getOpenFolder();
2398 if (openFolder.isEditingName()) {
2399 openFolder.dismissEditingName();
2400 } else {
2401 closeFolder();
2402 }
2403 } else {
2404 mWorkspace.exitWidgetResizeMode();
2405
2406 // Back button is a no-op here, but give at least some feedback for the button press
2407 mWorkspace.showOutlinesTemporarily();
2408 }
2409 }
2410
2411 /**
2412 * Re-listen when widgets are reset.
2413 */
2414 private void onAppWidgetReset() {
2415 if (mAppWidgetHost != null) {
2416 mAppWidgetHost.startListening();
2417 }
2418 }
2419
2420 /**
2421 * Launches the intent referred by the clicked shortcut.
2422 *
2423 * @param v The view representing the clicked shortcut.
2424 */
2425 public void onClick(View v) {
2426 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2427 // view has detached (it's possible for this to happen if the view is removed mid touch).
2428 if (v.getWindowToken() == null) {
2429 return;
2430 }
2431
2432 if (!mWorkspace.isFinishedSwitchingState()) {
2433 return;
2434 }
2435
2436 if (v instanceof Workspace) {
2437 if (mWorkspace.isInOverviewMode()) {
2438 mWorkspace.exitOverviewMode(true);
2439 }
2440 return;
2441 }
2442
2443 if (v instanceof CellLayout) {
2444 if (mWorkspace.isInOverviewMode()) {
2445 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2446 }
2447 }
2448
2449 Object tag = v.getTag();
2450 if (tag instanceof ShortcutInfo) {
2451 onClickAppShortcut(v);
2452 } else if (tag instanceof FolderInfo) {
2453 if (v instanceof FolderIcon) {
2454 onClickFolderIcon(v);
2455 }
2456 } else if (v == mAllAppsButton) {
2457 onClickAllAppsButton(v);
2458 } else if (tag instanceof AppInfo) {
2459 startAppShortcutOrInfoActivity(v);
2460 } else if (tag instanceof LauncherAppWidgetInfo) {
2461 if (v instanceof PendingAppWidgetHostView) {
2462 onClickPendingWidget((PendingAppWidgetHostView) v);
2463 }
2464 }
2465 }
2466
2467 public void onClickPagedViewIcon(View v) {
2468 startAppShortcutOrInfoActivity(v);
2469 }
2470
2471 public boolean onTouch(View v, MotionEvent event) {
2472 return false;
2473 }
2474
2475 /**
2476 * Event handler for the app widget view which has not fully restored.
2477 */
2478 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2479 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2480 if (v.isReadyForClickSetup()) {
2481 int widgetId = info.appWidgetId;
2482 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2483 if (appWidgetInfo != null) {
2484 mPendingAddWidgetInfo = appWidgetInfo;
2485 mPendingAddInfo.copyFrom(info);
2486 mPendingAddWidgetId = widgetId;
2487
2488 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2489 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2490 }
2491 } else if (info.installProgress < 0) {
2492 // The install has not been queued
2493 final String packageName = info.providerName.getPackageName();
2494 showBrokenAppInstallDialog(packageName,
2495 new DialogInterface.OnClickListener() {
2496 public void onClick(DialogInterface dialog, int id) {
2497 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2498 }
2499 });
2500 } else {
2501 // Download has started.
2502 final String packageName = info.providerName.getPackageName();
2503 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2504 }
2505 }
2506
2507 /**
2508 * Event handler for the search button
2509 *
2510 * @param v The view that was clicked.
2511 */
2512 public void onClickSearchButton(View v) {
2513 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2514
2515 onSearchRequested();
2516 }
2517
2518 /**
2519 * Event handler for the voice button
2520 *
2521 * @param v The view that was clicked.
2522 */
2523 public void onClickVoiceButton(View v) {
2524 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2525
2526 startVoice();
2527 }
2528
2529 public void startVoice() {
2530 try {
2531 final SearchManager searchManager =
2532 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2533 ComponentName activityName = searchManager.getGlobalSearchActivity();
2534 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2535 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2536 if (activityName != null) {
2537 intent.setPackage(activityName.getPackageName());
2538 }
2539 startActivity(null, intent, "onClickVoiceButton");
2540 } catch (ActivityNotFoundException e) {
2541 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2542 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2543 startActivitySafely(null, intent, "onClickVoiceButton");
2544 }
2545 }
2546
2547 /**
2548 * Event handler for the "grid" button that appears on the home screen, which
2549 * enters all apps mode.
2550 *
2551 * @param v The view that was clicked.
2552 */
2553 protected void onClickAllAppsButton(View v) {
2554 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2555 if (isAllAppsVisible()) {
2556 showWorkspace(true);
2557 } else {
2558 showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false);
2559 }
2560 }
2561
2562 private void showBrokenAppInstallDialog(final String packageName,
2563 DialogInterface.OnClickListener onSearchClickListener) {
2564 new AlertDialog.Builder(this)
2565 .setTitle(R.string.abandoned_promises_title)
2566 .setMessage(R.string.abandoned_promise_explanation)
2567 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2568 .setNeutralButton(R.string.abandoned_clean_this,
2569 new DialogInterface.OnClickListener() {
2570 public void onClick(DialogInterface dialog, int id) {
2571 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2572 mWorkspace.removeAbandonedPromise(packageName, user);
2573 }
2574 })
2575 .create().show();
2576 return;
2577 }
2578
2579 /**
2580 * Event handler for an app shortcut click.
2581 *
2582 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2583 */
2584 protected void onClickAppShortcut(final View v) {
2585 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2586 Object tag = v.getTag();
2587 if (!(tag instanceof ShortcutInfo)) {
2588 throw new IllegalArgumentException("Input must be a Shortcut");
2589 }
2590
2591 // Open shortcut
2592 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2593
2594 if (shortcut.isDisabled != 0) {
2595 int error = R.string.activity_not_available;
2596 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2597 error = R.string.safemode_shortcut_error;
2598 }
2599 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2600 return;
2601 }
2602
2603 final Intent intent = shortcut.intent;
2604
2605 // Check for special shortcuts
2606 if (intent.getComponent() != null) {
2607 final String shortcutClass = intent.getComponent().getClassName();
2608
2609 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2610 MemoryDumpActivity.startDump(this);
2611 return;
2612 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2613 toggleShowWeightWatcher();
2614 return;
2615 }
2616 }
2617
2618 // Check for abandoned promise
2619 if ((v instanceof BubbleTextView)
2620 && shortcut.isPromise()
2621 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2622 showBrokenAppInstallDialog(
2623 shortcut.getTargetComponent().getPackageName(),
2624 new DialogInterface.OnClickListener() {
2625 public void onClick(DialogInterface dialog, int id) {
2626 startAppShortcutOrInfoActivity(v);
2627 }
2628 });
2629 return;
2630 }
2631
2632 // Start activities
2633 startAppShortcutOrInfoActivity(v);
2634 }
2635
2636 private void startAppShortcutOrInfoActivity(View v) {
2637 Object tag = v.getTag();
2638 final ShortcutInfo shortcut;
2639 final Intent intent;
2640 if (tag instanceof ShortcutInfo) {
2641 shortcut = (ShortcutInfo) tag;
2642 intent = shortcut.intent;
2643 int[] pos = new int[2];
2644 v.getLocationOnScreen(pos);
2645 intent.setSourceBounds(new Rect(pos[0], pos[1],
2646 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2647
2648 } else if (tag instanceof AppInfo) {
2649 shortcut = null;
2650 intent = ((AppInfo) tag).intent;
2651 } else {
2652 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2653 }
2654
2655 boolean success = startActivitySafely(v, intent, tag);
2656 mStats.recordLaunch(intent, shortcut);
2657
2658 if (success && v instanceof BubbleTextView) {
2659 mWaitingForResume = (BubbleTextView) v;
2660 mWaitingForResume.setStayPressed(true);
2661 }
2662 }
2663
2664 /**
2665 * Event handler for a folder icon click.
2666 *
2667 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2668 */
2669 protected void onClickFolderIcon(View v) {
2670 if (LOGD) Log.d(TAG, "onClickFolder");
2671 if (!(v instanceof FolderIcon)){
2672 throw new IllegalArgumentException("Input must be a FolderIcon");
2673 }
2674
2675 FolderIcon folderIcon = (FolderIcon) v;
2676 final FolderInfo info = folderIcon.getFolderInfo();
2677 Folder openFolder = mWorkspace.getFolderForTag(info);
2678
2679 // If the folder info reports that the associated folder is open, then verify that
2680 // it is actually opened. There have been a few instances where this gets out of sync.
2681 if (info.opened && openFolder == null) {
2682 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2683 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2684 info.opened = false;
2685 }
2686
2687 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2688 // Close any open folder
2689 closeFolder();
2690 // Open the requested folder
2691 openFolder(folderIcon);
2692 } else {
2693 // Find the open folder...
2694 int folderScreen;
2695 if (openFolder != null) {
2696 folderScreen = mWorkspace.getPageForView(openFolder);
2697 // .. and close it
2698 closeFolder(openFolder);
2699 if (folderScreen != mWorkspace.getCurrentPage()) {
2700 // Close any folder open on the current screen
2701 closeFolder();
2702 // Pull the folder onto this screen
2703 openFolder(folderIcon);
2704 }
2705 }
2706 }
2707 }
2708
2709 /**
2710 * Event handler for the (Add) Widgets button that appears after a long press
2711 * on the home screen.
2712 */
2713 protected void onClickAddWidgetButton(View view) {
2714 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2715 showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
2716 }
2717
2718 /**
2719 * Event handler for the wallpaper picker button that appears after a long press
2720 * on the home screen.
2721 */
2722 protected void onClickWallpaperPicker(View v) {
2723 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2724 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
2725 pickWallpaper.setComponent(getWallpaperPickerComponent());
2726 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
2727 }
2728
2729 /**
2730 * Event handler for a click on the settings button that appears after a long press
2731 * on the home screen.
2732 */
2733 protected void onClickSettingsButton(View v) {
2734 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2735 }
2736
2737 public void onTouchDownAllAppsButton(View v) {
2738 // Provide the same haptic feedback that the system offers for virtual keys.
2739 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2740 }
2741
2742 public void performHapticFeedbackOnTouchDown(View v) {
2743 // Provide the same haptic feedback that the system offers for virtual keys.
2744 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2745 }
2746
2747 public View.OnTouchListener getHapticFeedbackTouchListener() {
2748 if (mHapticFeedbackTouchListener == null) {
2749 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2750 @Override
2751 public boolean onTouch(View v, MotionEvent event) {
2752 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2753 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2754 }
2755 return false;
2756 }
2757 };
2758 }
2759 return mHapticFeedbackTouchListener;
2760 }
2761
2762 public void onDragStarted(View view) {}
2763
2764 /**
2765 * Called when the user stops interacting with the launcher.
2766 * This implies that the user is now on the homescreen and is not doing housekeeping.
2767 */
2768 protected void onInteractionEnd() {}
2769
2770 /**
2771 * Called when the user starts interacting with the launcher.
2772 * The possible interactions are:
2773 * - open all apps
2774 * - reorder an app shortcut, or a widget
2775 * - open the overview mode.
2776 * This is a good time to stop doing things that only make sense
2777 * when the user is on the homescreen and not doing housekeeping.
2778 */
2779 protected void onInteractionBegin() {}
2780
2781 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2782 String packageName = componentName.getPackageName();
2783 try {
2784 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2785 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2786 launcherApps.showAppDetailsForProfile(componentName, user);
2787 } catch (SecurityException e) {
2788 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2789 Log.e(TAG, "Launcher does not have permission to launch settings");
2790 } catch (ActivityNotFoundException e) {
2791 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2792 Log.e(TAG, "Unable to launch settings");
2793 }
2794 }
2795
2796 // returns true if the activity was started
2797 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2798 UserHandleCompat user) {
2799 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2800 // System applications cannot be installed. For now, show a toast explaining that.
2801 // We may give them the option of disabling apps this way.
2802 int messageId = R.string.uninstall_system_app_text;
2803 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2804 return false;
2805 } else {
2806 String packageName = componentName.getPackageName();
2807 String className = componentName.getClassName();
2808 Intent intent = new Intent(
2809 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2810 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2811 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2812 if (user != null) {
2813 user.addToIntent(intent, Intent.EXTRA_USER);
2814 }
2815 startActivity(intent);
2816 return true;
2817 }
2818 }
2819
2820 boolean startActivity(View v, Intent intent, Object tag) {
2821 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2822 try {
2823 // Only launch using the new animation if the shortcut has not opted out (this is a
2824 // private contract between launcher and may be ignored in the future).
2825 boolean useLaunchAnimation = (v != null) &&
2826 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2827 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2828 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2829
2830 UserHandleCompat user = null;
2831 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2832 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2833 user = userManager.getUserForSerialNumber(serialNumber);
2834 }
2835
2836 Bundle optsBundle = null;
2837 if (useLaunchAnimation) {
2838 ActivityOptions opts = Utilities.isLmpOrAbove() ?
2839 ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim)🔵
2840 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
2841 optsBundle = opts.toBundle();
2842 }
2843
2844 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2845 // Could be launching some bookkeeping activity
2846 startActivity(intent, optsBundle);
2847 } else {
2848 // TODO Component can be null when shortcuts are supported for secondary user
2849 launcherApps.startActivityForProfile(intent.getComponent(), user,
2850 intent.getSourceBounds(), optsBundle);
2851 }
2852 return true;
2853 } catch (SecurityException e) {
2854 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2855 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2856 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2857 "or use the exported attribute for this activity. "
2858 + "tag="+ tag + " intent=" + intent, e);
2859 }
2860 return false;
2861 }
2862
2863 boolean startActivitySafely(View v, Intent intent, Object tag) {
2864 boolean success = false;
2865 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2866 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2867 return false;
2868 }
2869 try {
2870 success = startActivity(v, intent, tag);
2871 } catch (ActivityNotFoundException e) {
2872 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2873 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2874 }
2875 return success;
2876 }
2877
2878 /**
2879 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2880 * in the DragLayer in the exact absolute location of the original FolderIcon.
2881 */
2882 private void copyFolderIconToImage(FolderIcon fi) {
2883 final int width = fi.getMeasuredWidth();
2884 final int height = fi.getMeasuredHeight();
2885
2886 // Lazy load ImageView, Bitmap and Canvas
2887 if (mFolderIconImageView == null) {
2888 mFolderIconImageView = new ImageView(this);
2889 }
2890 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
2891 mFolderIconBitmap.getHeight() != height) {
2892 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
2893 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
2894 }
2895
2896 DragLayer.LayoutParams lp;
2897 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
2898 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
2899 } else {
2900 lp = new DragLayer.LayoutParams(width, height);
2901 }
2902
2903 // The layout from which the folder is being opened may be scaled, adjust the starting
2904 // view size by this scale factor.
2905 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
2906 lp.customPosition = true;
2907 lp.x = mRectForFolderAnimation.left;
2908 lp.y = mRectForFolderAnimation.top;
2909 lp.width = (int) (scale * width);
2910 lp.height = (int) (scale * height);
2911
2912 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
2913 fi.draw(mFolderIconCanvas);
2914 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
2915 if (fi.getFolder() != null) {
2916 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
2917 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
2918 }
2919 // Just in case this image view is still in the drag layer from a previous animation,
2920 // we remove it and re-add it.
2921 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
2922 mDragLayer.removeView(mFolderIconImageView);
2923 }
2924 mDragLayer.addView(mFolderIconImageView, lp);
2925 if (fi.getFolder() != null) {
2926 fi.getFolder().bringToFront();
2927 }
2928 }
2929
2930 private void growAndFadeOutFolderIcon(FolderIcon fi) {
2931 if (fi == null) return;
2932 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
2933 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
2934 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
2935
2936 FolderInfo info = (FolderInfo) fi.getTag();
2937 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2938 CellLayout cl = (CellLayout) fi.getParent().getParent();
2939 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
2940 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
2941 }
2942
2943 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
2944 copyFolderIconToImage(fi);
2945 fi.setVisibility(View.INVISIBLE);
2946
2947 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
2948 scaleX, scaleY);
2949 if (Utilities.isLmpOrAbove()) {
2950 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
2951 }
2952 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
2953 oa.start();
2954 }
2955
2956 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
2957 if (fi == null) return;
2958 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
2959 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
2960 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
2961
2962 final CellLayout cl = (CellLayout) fi.getParent().getParent();
2963
2964 // We remove and re-draw the FolderIcon in-case it has changed
2965 mDragLayer.removeView(mFolderIconImageView);
2966 copyFolderIconToImage(fi);
2967 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
2968 scaleX, scaleY);
2969 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
2970 oa.addListener(new AnimatorListenerAdapter() {
2971 @Override
2972 public void onAnimationEnd(Animator animation) {
2973 if (cl != null) {
2974 cl.clearFolderLeaveBehind();
2975 // Remove the ImageView copy of the FolderIcon and make the original visible.
2976 mDragLayer.removeView(mFolderIconImageView);
2977 fi.setVisibility(View.VISIBLE);
2978 }
2979 }
2980 });
2981 oa.start();
2982 }
2983
2984 /**
2985 * Opens the user folder described by the specified tag. The opening of the folder
2986 * is animated relative to the specified View. If the View is null, no animation
2987 * is played.
2988 *
2989 * @param folderInfo The FolderInfo describing the folder to open.
2990 */
2991 public void openFolder(FolderIcon folderIcon) {
2992 Folder folder = folderIcon.getFolder();
2993 FolderInfo info = folder.mInfo;
2994
2995 info.opened = true;
2996
2997 // Just verify that the folder hasn't already been added to the DragLayer.
2998 // There was a one-off crash where the folder had a parent already.
2999 if (folder.getParent() == null) {
3000 mDragLayer.addView(folder);
3001 mDragController.addDropTarget((DropTarget) folder);
3002 } else {
3003 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3004 folder.getParent() + ").");
3005 }
3006 folder.animateOpen();
3007 growAndFadeOutFolderIcon(folderIcon);
3008
3009 // Notify the accessibility manager that this folder "window" has appeared and occluded
3010 // the workspace items
3011 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3012 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3013 }
3014
3015 public void closeFolder() {
3016 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3017 if (folder != null) {
3018 if (folder.isEditingName()) {
3019 folder.dismissEditingName();
3020 }
3021 closeFolder(folder);
3022 }
3023 }
3024
3025 void closeFolder(Folder folder) {
3026 folder.getInfo().opened = false;
3027
3028 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3029 if (parent != null) {
3030 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3031 shrinkAndFadeInFolderIcon(fi);
3032 }
3033 folder.animateClosed();
3034
3035 // Notify the accessibility manager that this folder "window" has disappeard and no
3036 // longer occludeds the workspace items
3037 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3038 }
3039
3040 public boolean onLongClick(View v) {
3041 if (!isDraggingEnabled()) return false;
3042 if (isWorkspaceLocked()) return false;
3043 if (mState != State.WORKSPACE) return false;
3044
3045 if (v instanceof Workspace) {
3046 if (!mWorkspace.isInOverviewMode()) {
3047 if (mWorkspace.enterOverviewMode()) {
3048 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3049 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3050 return true;
3051 } else {
3052 return false;
3053 }
3054 } else {
3055 return false;
3056 }
3057 }
3058
3059 CellLayout.CellInfo longClickCellInfo = null;
3060 View itemUnderLongClick = null;
3061 if (v.getTag() instanceof ItemInfo) {
3062 ItemInfo info = (ItemInfo) v.getTag();
3063 longClickCellInfo = new CellLayout.CellInfo(v, info);;
3064 itemUnderLongClick = longClickCellInfo.cell;
3065 resetAddInfo();
3066 }
3067
3068 // The hotseat touch handling does not go through Workspace, and we always allow long press
3069 // on hotseat items.
3070 final boolean inHotseat = isHotseatLayout(v);
3071 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3072 if (allowLongPress && !mDragController.isDragging()) {
3073 if (itemUnderLongClick == null) {
3074 // User long pressed on empty space
3075 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3076 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3077 if (mWorkspace.isInOverviewMode()) {
3078 mWorkspace.startReordering(v);
3079 } else {
3080 mWorkspace.enterOverviewMode();
3081 }
3082 } else {
3083 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3084 mHotseat.getOrderInHotseat(
3085 longClickCellInfo.cellX,
3086 longClickCellInfo.cellY));
3087 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3088 // User long pressed on an item
3089 mWorkspace.startDrag(longClickCellInfo);
3090 }
3091 }
3092 }
3093 return true;
3094 }
3095
3096 boolean isHotseatLayout(View layout) {
3097 return mHotseat != null && layout != null &&
3098 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3099 }
3100
3101 /**
3102 * Returns the CellLayout of the specified container at the specified screen.
3103 */
3104 CellLayout getCellLayout(long container, long screenId) {
3105 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3106 if (mHotseat != null) {
3107 return mHotseat.getLayout();
3108 } else {
3109 return null;
3110 }
3111 } else {
3112 return (CellLayout) mWorkspace.getScreenWithId(screenId);
3113 }
3114 }
3115
3116 public boolean isAllAppsVisible() {
3117 return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
3118 }
3119
3120 private void setWorkspaceBackground(boolean workspace) {
3121 mLauncherView.setBackground(workspace ?
3122 mWorkspaceBackgroundDrawable : null);
3123 }
3124
3125 protected void changeWallpaperVisiblity(boolean visible) {
3126 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3127 int curflags = getWindow().getAttributes().flags
3128 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3129 if (wpflags != curflags) {
3130 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3131 }
3132 setWorkspaceBackground(visible);
3133 }
3134
3135 private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
3136 if (v instanceof LauncherTransitionable) {
3137 ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
3138 }
3139 }
3140
3141 private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
3142 if (v instanceof LauncherTransitionable) {
3143 ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
3144 }
3145
3146 // Update the workspace transition step as well
3147 dispatchOnLauncherTransitionStep(v, 0f);
3148 }
3149
3150 private void dispatchOnLauncherTransitionStep(View v, float t) {
3151 if (v instanceof LauncherTransitionable) {
3152 ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
3153 }
3154 }
3155
3156 private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
3157 if (v instanceof LauncherTransitionable) {
3158 ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
3159 }
3160
3161 // Update the workspace transition step as well
3162 dispatchOnLauncherTransitionStep(v, 1f);
3163 }
3164
3165 /**
3166 * Things to test when changing the following seven functions.
3167 * - Home from workspace
3168 * - from center screen
3169 * - from other screens
3170 * - Home from all apps
3171 * - from center screen
3172 * - from other screens
3173 * - Back from all apps
3174 * - from center screen
3175 * - from other screens
3176 * - Launch app from workspace and quit
3177 * - with back
3178 * - with home
3179 * - Launch app from all apps and quit
3180 * - with back
3181 * - with home
3182 * - Go to a screen that's not the default, then all
3183 * apps, and launch and app, and go back
3184 * - with back
3185 * -with home
3186 * - On workspace, long press power and go back
3187 * - with back
3188 * - with home
3189 * - On all apps, long press power and go back
3190 * - with back
3191 * - with home
3192 * - On workspace, power off
3193 * - On all apps, power off
3194 * - Launch an app and turn off the screen while in that app
3195 * - Go back with home key
3196 * - Go back with back key TODO: make this not go to workspace
3197 * - From all apps
3198 * - From workspace
3199 * - Enter and exit car mode (becuase it causes an extra configuration changed)
3200 * - From all apps
3201 * - From the center workspace
3202 * - From another workspace
3203 */
3204
3205 /**
3206 * Zoom the camera out from the workspace to reveal 'toView'.
3207 * Assumes that the view to show is anchored at either the very top or very bottom
3208 * of the screen.
3209 */
3210 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
3211 AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
3212 showAppsCustomizeHelper(animated, springLoaded, contentType);
3213 }
3214
3215 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
3216 final AppsCustomizePagedView.ContentType contentType) {
3217 if (mStateAnimation != null) {
3218 mStateAnimation.setDuration(0);
3219 mStateAnimation.cancel();
3220 mStateAnimation = null;
3221 }
3222
3223 boolean material = Utilities.isLmpOrAbove();
3224
3225 final Resources res = getResources();
3226
3227 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
3228 final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
3229 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
3230 final int itemsAlphaStagger =
3231 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
3232
3233 final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
3234 final View fromView = mWorkspace;
3235 final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
3236
3237 final ArrayList<View> layerViews = new ArrayList<View>();
3238
3239 Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ?
3240 Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN;
3241 Animator workspaceAnim =
3242 mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews);
3243 if (!LauncherAppState.isDisableAllApps()
3244 || contentType == AppsCustomizePagedView.ContentType.Widgets) {
3245 // Set the content type for the all apps/widgets space
3246 mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
3247 }
3248
3249 // If for some reason our views aren't initialized, don't animate
3250 boolean initialized = getAllAppsButton() != null;
3251
3252 if (animated && initialized) {
3253 mStateAnimation = LauncherAnimUtils.createAnimatorSet();
3254 final AppsCustomizePagedView content = (AppsCustomizePagedView)
3255 toView.findViewById(R.id.apps_customize_pane_content);
3256
3257 final View page = content.getPageAt(content.getCurrentPage());
3258 final View revealView = toView.findViewById(R.id.fake_page);
3259
3260 final float initialPanelAlpha = 1f;
3261
3262 final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets;
3263 if (isWidgetTray) {
3264 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
3265 } else {
3266 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
3267 }
3268
3269 // Hide the real page background, and swap in the fake one
3270 content.setPageBackgroundsVisible(false);
3271 revealView.setVisibility(View.VISIBLE);
3272 // We need to hide this view as the animation start will be posted.
3273 revealView.setAlpha(0);
3274
3275 int width = revealView.getMeasuredWidth();
3276 int height = revealView.getMeasuredHeight();
3277 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
3278
3279 revealView.setTranslationY(0);
3280 revealView.setTranslationX(0);
3281
3282 // Get the y delta between the center of the page and the center of the all apps button
3283 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
3284 getAllAppsButton(), null);
3285
3286 float alpha = 0;
3287 float xDrift = 0;
3288 float yDrift = 0;
3289 if (material) {
3290 alpha = isWidgetTray ? 0.3f : 1f;
3291 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
3292 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
3293 } else {
3294 yDrift = 2 * height / 3;
3295 xDrift = 0;
3296 }
3297 final float initAlpha = alpha;
3298
3299 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3300 layerViews.add(revealView);
3301 PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f);
3302 PropertyValuesHolder panelDriftY =
3303 PropertyValuesHolder.ofFloat("translationY", yDrift, 0);
3304 PropertyValuesHolder panelDriftX =
3305 PropertyValuesHolder.ofFloat("translationX", xDrift, 0);
3306
3307 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
3308 panelAlpha, panelDriftY, panelDriftX);
3309
3310 panelAlphaAndDrift.setDuration(revealDuration);
3311 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
3312
3313 mStateAnimation.play(panelAlphaAndDrift);
3314
3315 if (page != null) {
3316 page.setVisibility(View.VISIBLE);
3317 page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3318 layerViews.add(page);
3319
3320 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0);
3321 page.setTranslationY(yDrift);
3322 pageDrift.setDuration(revealDuration);
3323 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
3324 pageDrift.setStartDelay(itemsAlphaStagger);
3325 mStateAnimation.play(pageDrift);
3326
3327 page.setAlpha(0f);
3328 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f);
3329 itemsAlpha.setDuration(revealDuration);
3330 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
3331 itemsAlpha.setStartDelay(itemsAlphaStagger);
3332 mStateAnimation.play(itemsAlpha);
3333 }
3334
3335 View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator);
3336 pageIndicators.setAlpha(0.01f);
3337 ObjectAnimator indicatorsAlpha =
3338 ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f);
3339 indicatorsAlpha.setDuration(revealDuration);
3340 mStateAnimation.play(indicatorsAlpha);
3341
3342 if (material) {
3343 final View allApps = getAllAppsButton();
3344 int allAppsButtonSize = LauncherAppState.getInstance().
3345 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
3346 float startRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
3347 Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2,
3348 height / 2, startRadius, revealRadius);
3349 reveal.setDuration(revealDuration);
3350 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
3351
3352 reveal.addListener(new AnimatorListenerAdapter() {
3353 public void onAnimationStart(Animator animation) {
3354 if (!isWidgetTray) {
3355 allApps.setVisibility(View.INVISIBLE);
3356 }
3357 }
3358 public void onAnimationEnd(Animator animation) {
3359 if (!isWidgetTray) {
3360 allApps.setVisibility(View.VISIBLE);
3361 }
3362 }
3363 });
3364 mStateAnimation.play(reveal);
3365 }
3366
3367 mStateAnimation.addListener(new AnimatorListenerAdapter() {
3368 @Override
3369 public void onAnimationEnd(Animator animation) {
3370 dispatchOnLauncherTransitionEnd(fromView, animated, false);
3371 dispatchOnLauncherTransitionEnd(toView, animated, false);
3372
3373 revealView.setVisibility(View.INVISIBLE);
3374 revealView.setLayerType(View.LAYER_TYPE_NONE, null);
3375 if (page != null) {
3376 page.setLayerType(View.LAYER_TYPE_NONE, null);
3377 }
3378 content.setPageBackgroundsVisible(true);
3379
3380 // Hide the search bar
3381 if (mSearchDropTargetBar != null) {
3382 mSearchDropTargetBar.hideSearchBar(false);
3383 }
3384 }
3385
3386 });
3387
3388 if (workspaceAnim != null) {
3389 mStateAnimation.play(workspaceAnim);
3390 }
3391
3392 dispatchOnLauncherTransitionPrepare(fromView, animated, false);
3393 dispatchOnLauncherTransitionPrepare(toView, animated, false);
3394 final AnimatorSet stateAnimation = mStateAnimation;
3395 final Runnable startAnimRunnable = new Runnable() {
3396 public void run() {
3397 // Check that mStateAnimation hasn't changed while
3398 // we waited for a layout/draw pass
3399 if (mStateAnimation != stateAnimation)
3400 return;
3401 dispatchOnLauncherTransitionStart(fromView, animated, false);
3402 dispatchOnLauncherTransitionStart(toView, animated, false);
3403
3404 revealView.setAlpha(initAlpha);
3405 if (Utilities.isLmpOrAbove()) {
3406 for (int i = 0; i < layerViews.size(); i++) {
3407 View v = layerViews.get(i);
3408 if (v != null) {
3409 boolean attached = true;
3410 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3411 attached = v.isAttachedToWindow();
3412 }
3413 if (attached) v.buildLayer();
3414 }
3415 }
3416 }
3417 mStateAnimation.start();
3418 }
3419 };
3420 toView.bringToFront();
3421 toView.setVisibility(View.VISIBLE);
3422 toView.post(startAnimRunnable);
3423 } else {
3424 toView.setTranslationX(0.0f);
3425 toView.setTranslationY(0.0f);
3426 toView.setScaleX(1.0f);
3427 toView.setScaleY(1.0f);
3428 toView.setVisibility(View.VISIBLE);
3429 toView.bringToFront();
3430
3431 if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) {
3432 // Hide the search bar
3433 if (mSearchDropTargetBar != null) {
3434 mSearchDropTargetBar.hideSearchBar(false);
3435 }
3436 }
3437 dispatchOnLauncherTransitionPrepare(fromView, animated, false);
3438 dispatchOnLauncherTransitionStart(fromView, animated, false);
3439 dispatchOnLauncherTransitionEnd(fromView, animated, false);
3440 dispatchOnLauncherTransitionPrepare(toView, animated, false);
3441 dispatchOnLauncherTransitionStart(toView, animated, false);
3442 dispatchOnLauncherTransitionEnd(toView, animated, false);
3443 }
3444 }
3445
3446 /**
3447 * Zoom the camera back into the workspace, hiding 'fromView'.
3448 * This is the opposite of showAppsCustomizeHelper.
3449 * @param animated If true, the transition will be animated.
3450 */
3451 private void hideAppsCustomizeHelper(Workspace.State toState, final boolean animated,
3452 final boolean springLoaded, final Runnable onCompleteRunnable) {
3453
3454 if (mStateAnimation != null) {
3455 mStateAnimation.setDuration(0);
3456 mStateAnimation.cancel();
3457 mStateAnimation = null;
3458 }
3459
3460 boolean material = Utilities.isLmpOrAbove();
3461 Resources res = getResources();
3462
3463 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
3464 final int fadeOutDuration = res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
3465 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeConcealTime);
3466 final int itemsAlphaStagger =
3467 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
3468
3469 final float scaleFactor = (float)
3470 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
3471 final View fromView = mAppsCustomizeTabHost;
3472 final View toView = mWorkspace;
3473 Animator workspaceAnim = null;
3474 final ArrayList<View> layerViews = new ArrayList<View>();
3475
3476 if (toState == Workspace.State.NORMAL) {
3477 workspaceAnim = mWorkspace.getChangeStateAnimation(
3478 toState, animated, layerViews);
3479 } else if (toState == Workspace.State.SPRING_LOADED ||
3480 toState == Workspace.State.OVERVIEW) {
3481 workspaceAnim = mWorkspace.getChangeStateAnimation(
3482 toState, animated, layerViews);
3483 }
3484
3485 // If for some reason our views aren't initialized, don't animate
3486 boolean initialized = getAllAppsButton() != null;
3487
3488 if (animated && initialized) {
3489 mStateAnimation = LauncherAnimUtils.createAnimatorSet();
3490 if (workspaceAnim != null) {
3491 mStateAnimation.play(workspaceAnim);
3492 }
3493
3494 final AppsCustomizePagedView content = (AppsCustomizePagedView)
3495 fromView.findViewById(R.id.apps_customize_pane_content);
3496
3497 final View page = content.getPageAt(content.getNextPage());
3498
3499 // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases
3500 int count = content.getChildCount();
3501 for (int i = 0; i < count; i++) {
3502 View child = content.getChildAt(i);
3503 if (child != page) {
3504 child.setVisibility(View.INVISIBLE);
3505 }
3506 }
3507 final View revealView = fromView.findViewById(R.id.fake_page);
3508
3509 // hideAppsCustomizeHelper is called in some cases when it is already hidden
3510 // don't perform all these no-op animations. In particularly, this was causing
3511 // the all-apps button to pop in and out.
3512 if (fromView.getVisibility() == View.VISIBLE) {
3513 AppsCustomizePagedView.ContentType contentType = content.getContentType();
3514 final boolean isWidgetTray =
3515 contentType == AppsCustomizePagedView.ContentType.Widgets;
3516
3517 if (isWidgetTray) {
3518 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
3519 } else {
3520 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
3521 }
3522
3523 int width = revealView.getMeasuredWidth();
3524 int height = revealView.getMeasuredHeight();
3525 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
3526
3527 // Hide the real page background, and swap in the fake one
3528 revealView.setVisibility(View.VISIBLE);
3529 content.setPageBackgroundsVisible(false);
3530
3531 final View allAppsButton = getAllAppsButton();
3532 revealView.setTranslationY(0);
3533 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
3534 allAppsButton, null);
3535
3536 float xDrift = 0;
3537 float yDrift = 0;
3538 if (material) {
3539 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
3540 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
3541 } else {
3542 yDrift = 5 * height / 4;
3543 xDrift = 0;
3544 }
3545
3546 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3547 TimeInterpolator decelerateInterpolator = material ?
3548 new LogDecelerateInterpolator(100, 0) :
3549 new LogDecelerateInterpolator(30, 0);
3550
3551 // The vertical motion of the apps panel should be delayed by one frame
3552 // from the conceal animation in order to give the right feel. We correpsondingly
3553 // shorten the duration so that the slide and conceal end at the same time.
3554 ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY",
3555 0, yDrift);
3556 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3557 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3558 panelDriftY.setInterpolator(decelerateInterpolator);
3559 mStateAnimation.play(panelDriftY);
3560
3561 ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX",
3562 0, xDrift);
3563 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3564 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3565 panelDriftX.setInterpolator(decelerateInterpolator);
3566 mStateAnimation.play(panelDriftX);
3567
3568 if (isWidgetTray || !material) {
3569 float finalAlpha = material ? 0.4f : 0f;
3570 revealView.setAlpha(1f);
3571 ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha",
3572 1f, finalAlpha);
3573 panelAlpha.setDuration(revealDuration);
3574 panelAlpha.setInterpolator(material ? decelerateInterpolator :
3575 new AccelerateInterpolator(1.5f));
3576 mStateAnimation.play(panelAlpha);
3577 }
3578
3579 if (page != null) {
3580 page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3581
3582 ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(page, "translationY",
3583 0, yDrift);
3584 page.setTranslationY(0);
3585 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3586 pageDrift.setInterpolator(decelerateInterpolator);
3587 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3588 mStateAnimation.play(pageDrift);
3589
3590 page.setAlpha(1f);
3591 ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(page, "alpha", 1f, 0f);
3592 itemsAlpha.setDuration(100);
3593 itemsAlpha.setInterpolator(decelerateInterpolator);
3594 mStateAnimation.play(itemsAlpha);
3595 }
3596
3597 View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator);
3598 pageIndicators.setAlpha(1f);
3599 ObjectAnimator indicatorsAlpha =
3600 LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f);
3601 indicatorsAlpha.setDuration(revealDuration);
3602 indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f));
3603 mStateAnimation.play(indicatorsAlpha);
3604
3605 width = revealView.getMeasuredWidth();
3606
3607 if (material) {
3608 if (!isWidgetTray) {
3609 allAppsButton.setVisibility(View.INVISIBLE);
3610 }
3611 int allAppsButtonSize = LauncherAppState.getInstance().
3612 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
3613 float finalRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
3614 Animator reveal =
3615 LauncherAnimUtils.createCircularReveal(revealView, width / 2,
3616 height / 2, revealRadius, finalRadius);
3617 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
3618 reveal.setDuration(revealDuration);
3619 reveal.setStartDelay(itemsAlphaStagger);
3620
3621 reveal.addListener(new AnimatorListenerAdapter() {
3622 public void onAnimationEnd(Animator animation) {
3623 revealView.setVisibility(View.INVISIBLE);
3624 if (!isWidgetTray) {
3625 allAppsButton.setVisibility(View.VISIBLE);
3626 }
3627 }
3628 });
3629
3630 mStateAnimation.play(reveal);
3631 }
3632
3633 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
3634 dispatchOnLauncherTransitionPrepare(toView, animated, true);
3635 mAppsCustomizeContent.stopScrolling();
3636 }
3637
3638 mStateAnimation.addListener(new AnimatorListenerAdapter() {
3639 @Override
3640 public void onAnimationEnd(Animator animation) {
3641 fromView.setVisibility(View.GONE);
3642 dispatchOnLauncherTransitionEnd(fromView, animated, true);
3643 dispatchOnLauncherTransitionEnd(toView, animated, true);
3644 if (onCompleteRunnable != null) {
3645 onCompleteRunnable.run();
3646 }
3647
3648 revealView.setLayerType(View.LAYER_TYPE_NONE, null);
3649 if (page != null) {
3650 page.setLayerType(View.LAYER_TYPE_NONE, null);
3651 }
3652 content.setPageBackgroundsVisible(true);
3653 // Unhide side pages
3654 int count = content.getChildCount();
3655 for (int i = 0; i < count; i++) {
3656 View child = content.getChildAt(i);
3657 child.setVisibility(View.VISIBLE);
3658 }
3659
3660 // Reset page transforms
3661 if (page != null) {
3662 page.setTranslationX(0);
3663 page.setTranslationY(0);
3664 page.setAlpha(1);
3665 }
3666 content.setCurrentPage(content.getNextPage());
3667
3668 mAppsCustomizeContent.updateCurrentPageScroll();
3669 }
3670 });
3671
3672 final AnimatorSet stateAnimation = mStateAnimation;
3673 final Runnable startAnimRunnable = new Runnable() {
3674 public void run() {
3675 // Check that mStateAnimation hasn't changed while
3676 // we waited for a layout/draw pass
3677 if (mStateAnimation != stateAnimation)
3678 return;
3679 dispatchOnLauncherTransitionStart(fromView, animated, false);
3680 dispatchOnLauncherTransitionStart(toView, animated, false);
3681
3682 if (Utilities.isLmpOrAbove()) {
3683 for (int i = 0; i < layerViews.size(); i++) {
3684 View v = layerViews.get(i);
3685 if (v != null) {
3686 boolean attached = true;
3687 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3688 attached = v.isAttachedToWindow();
3689 }
3690 if (attached) v.buildLayer();
3691 }
3692 }
3693 }
3694 mStateAnimation.start();
3695 }
3696 };
3697 fromView.post(startAnimRunnable);
3698 } else {
3699 fromView.setVisibility(View.GONE);
3700 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
3701 dispatchOnLauncherTransitionStart(fromView, animated, true);
3702 dispatchOnLauncherTransitionEnd(fromView, animated, true);
3703 dispatchOnLauncherTransitionPrepare(toView, animated, true);
3704 dispatchOnLauncherTransitionStart(toView, animated, true);
3705 dispatchOnLauncherTransitionEnd(toView, animated, true);
3706 }
3707 }
3708
3709 @Override
3710 public void onTrimMemory(int level) {
3711 super.onTrimMemory(level);
3712 if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
3713 mAppsCustomizeTabHost.onTrimMemory();
3714 }
3715 }
3716
3717 protected void showWorkspace(boolean animated) {
3718 showWorkspace(animated, null);
3719 }
3720
3721 protected void showWorkspace() {
3722 showWorkspace(true);
3723 }
3724
3725 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3726 if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) {
3727 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3728 mWorkspace.setVisibility(View.VISIBLE);
3729 hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable);
3730
3731 // Show the search bar (only animate if we were showing the drop target bar in spring
3732 // loaded mode)
3733 if (mSearchDropTargetBar != null) {
3734 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3735 }
3736
3737 // Set focus to the AppsCustomize button
3738 if (mAllAppsButton != null) {
3739 mAllAppsButton.requestFocus();
3740 }
3741 }
3742
3743 // Change the state *after* we've called all the transition code
3744 mState = State.WORKSPACE;
3745
3746 // Resume the auto-advance of widgets
3747 mUserPresent = true;
3748 updateRunning();
3749
3750 // Send an accessibility event to announce the context change
3751 getWindow().getDecorView()
3752 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3753
3754 onWorkspaceShown(animated);
3755 }
3756
3757 void showOverviewMode(boolean animated) {
3758 mWorkspace.setVisibility(View.VISIBLE);
3759 hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null);
3760 mState = State.WORKSPACE;
3761 onWorkspaceShown(animated);
3762 }
3763
3764 public void onWorkspaceShown(boolean animated) {
3765 }
3766
3767 void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
3768 boolean resetPageToZero) {
3769 if (mState != State.WORKSPACE) return;
3770
3771 if (resetPageToZero) {
3772 mAppsCustomizeTabHost.reset();
3773 }
3774 showAppsCustomizeHelper(animated, false, contentType);
3775 mAppsCustomizeTabHost.post(new Runnable() {
3776 @Override
3777 public void run() {
3778 // We post this in-case the all apps view isn't yet constructed.
3779 mAppsCustomizeTabHost.requestFocus();
3780 }
3781 });
3782
3783 // Change the state *after* we've called all the transition code
3784 mState = State.APPS_CUSTOMIZE;
3785
3786 // Pause the auto-advance of widgets until we are out of AllApps
3787 mUserPresent = false;
3788 updateRunning();
3789 closeFolder();
3790
3791 // Send an accessibility event to announce the context change
3792 getWindow().getDecorView()
3793 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3794 }
3795
3796 void enterSpringLoadedDragMode() {
3797 if (isAllAppsVisible()) {
3798 hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null);
3799 mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
3800 }
3801 }
3802
3803 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3804 final Runnable onCompleteRunnable) {
3805 if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
3806
3807 mHandler.postDelayed(new Runnable() {
3808 @Override
3809 public void run() {
3810 if (successfulDrop) {
3811 // Before we show workspace, hide all apps again because
3812 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3813 // clean up our state transition functions
3814 mAppsCustomizeTabHost.setVisibility(View.GONE);
3815 showWorkspace(true, onCompleteRunnable);
3816 } else {
3817 exitSpringLoadedDragMode();
3818 }
3819 }
3820 }, delay);
3821 }
3822
3823 void exitSpringLoadedDragMode() {
3824 if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
3825 final boolean animated = true;
3826 final boolean springLoaded = true;
3827 showAppsCustomizeHelper(animated, springLoaded);
3828 mState = State.APPS_CUSTOMIZE;
3829 }
3830 // Otherwise, we are not in spring loaded mode, so don't do anything.
3831 }
3832
3833 void lockAllApps() {
3834 // TODO
3835 }
3836
3837 void unlockAllApps() {
3838 // TODO
3839 }
3840
3841 /**
3842 * Hides the hotseat area.
3843 */
3844 void hideHotseat(boolean animated) {
3845 if (!LauncherAppState.getInstance().isScreenLarge()) {
3846 if (animated) {
3847 if (mHotseat.getAlpha() != 0f) {
3848 int duration = 0;
3849 if (mSearchDropTargetBar != null) {
3850 duration = mSearchDropTargetBar.getTransitionOutDuration();
3851 }
3852 mHotseat.animate().alpha(0f).setDuration(duration);
3853 }
3854 } else {
3855 mHotseat.setAlpha(0f);
3856 }
3857 }
3858 }
3859
3860 /**
3861 * Add an item from all apps or customize onto the given workspace screen.
3862 * If layout is null, add to the current screen.
3863 */
3864 void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
3865 if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
3866 showOutOfSpaceMessage(isHotseatLayout(layout));
3867 }
3868 }
3869
3870 /** Maps the current orientation to an index for referencing orientation correct global icons */
3871 private int getCurrentOrientationIndexForGlobalIcons() {
3872 // default - 0, landscape - 1
3873 switch (getResources().getConfiguration().orientation) {
3874 case Configuration.ORIENTATION_LANDSCAPE:
3875 return 1;
3876 default:
3877 return 0;
3878 }
3879 }
3880
3881 private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
3882 try {
3883 PackageManager packageManager = getPackageManager();
3884 // Look for the toolbar icon specified in the activity meta-data
3885 Bundle metaData = packageManager.getActivityInfo(
3886 activityName, PackageManager.GET_META_DATA).metaData;
3887 if (metaData != null) {
3888 int iconResId = metaData.getInt(resourceName);
3889 if (iconResId != 0) {
3890 Resources res = packageManager.getResourcesForActivity(activityName);
3891 return res.getDrawable(iconResId);
3892 }
3893 }
3894 } catch (NameNotFoundException e) {
3895 // This can happen if the activity defines an invalid drawable
3896 Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
3897 " not found", e);
3898 } catch (Resources.NotFoundException nfe) {
3899 // This can happen if the activity defines an invalid drawable
3900 Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
3901 nfe);
3902 }
3903 return null;
3904 }
3905
3906 // if successful in getting icon, return it; otherwise, set button to use default drawable
3907 private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
3908 int buttonId, ComponentName activityName, int fallbackDrawableId,
3909 String toolbarResourceName) {
3910 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
3911 Resources r = getResources();
3912 int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
3913 int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
3914
3915 TextView button = (TextView) findViewById(buttonId);
3916 // If we were unable to find the icon via the meta-data, use a generic one
3917 if (toolbarIcon == null) {
3918 toolbarIcon = r.getDrawable(fallbackDrawableId);
3919 toolbarIcon.setBounds(0, 0, w, h);
3920 if (button != null) {
3921 button.setCompoundDrawables(toolbarIcon, null, null, null);
3922 }
3923 return null;
3924 } else {
3925 toolbarIcon.setBounds(0, 0, w, h);
3926 if (button != null) {
3927 button.setCompoundDrawables(toolbarIcon, null, null, null);
3928 }
3929 return toolbarIcon.getConstantState();
3930 }
3931 }
3932
3933 // if successful in getting icon, return it; otherwise, set button to use default drawable
3934 private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
3935 int buttonId, ComponentName activityName, int fallbackDrawableId,
3936 String toolbarResourceName) {
3937 ImageView button = (ImageView) findViewById(buttonId);
3938 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
3939
3940 if (button != null) {
3941 // If we were unable to find the icon via the meta-data, use a
3942 // generic one
3943 if (toolbarIcon == null) {
3944 button.setImageResource(fallbackDrawableId);
3945 } else {
3946 button.setImageDrawable(toolbarIcon);
3947 }
3948 }
3949
3950 return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
3951
3952 }
3953
3954 private void updateTextButtonWithDrawable(int buttonId, Drawable d) {
3955 TextView button = (TextView) findViewById(buttonId);
3956 button.setCompoundDrawables(d, null, null, null);
3957 }
3958
3959 private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
3960 ImageView button = (ImageView) findViewById(buttonId);
3961 button.setImageDrawable(d.newDrawable(getResources()));
3962 }
3963
3964 private void invalidatePressedFocusedStates(View container, View button) {
3965 if (container instanceof HolographicLinearLayout) {
3966 HolographicLinearLayout layout = (HolographicLinearLayout) container;
3967 layout.invalidatePressedFocusedStates();
3968 } else if (button instanceof HolographicImageView) {
3969 HolographicImageView view = (HolographicImageView) button;
3970 view.invalidatePressedFocusedStates();
3971 }
3972 }
3973
3974 public View getQsbBar() {
3975 if (mQsb == null) {
3976 mQsb = mInflater.inflate(R.layout.qsb, mSearchDropTargetBar, false);
3977 mSearchDropTargetBar.addView(mQsb);
3978 }
3979 return mQsb;
3980 }
3981
3982 protected boolean updateGlobalSearchIcon() {
3983 final View searchButtonContainer = findViewById(R.id.search_button_container);
3984 final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
3985 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
3986 final View voiceButton = findViewById(R.id.voice_button);
3987
3988 final SearchManager searchManager =
3989 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
3990 ComponentName activityName = searchManager.getGlobalSearchActivity();
3991 if (activityName != null) {
3992 int coi = getCurrentOrientationIndexForGlobalIcons();
3993 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3994 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
3995 TOOLBAR_SEARCH_ICON_METADATA_NAME);
3996 if (sGlobalSearchIcon[coi] == null) {
3997 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3998 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
3999 TOOLBAR_ICON_METADATA_NAME);
4000 }
4001
4002 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
4003 searchButton.setVisibility(View.VISIBLE);
4004 invalidatePressedFocusedStates(searchButtonContainer, searchButton);
4005 return true;
4006 } else {
4007 // We disable both search and voice search when there is no global search provider
4008 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
4009 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
4010 if (searchButton != null) searchButton.setVisibility(View.GONE);
4011 if (voiceButton != null) voiceButton.setVisibility(View.GONE);
4012 updateVoiceButtonProxyVisible(false);
4013 return false;
4014 }
4015 }
4016
4017 protected void updateGlobalSearchIcon(Drawable.ConstantState d) {
4018 final View searchButtonContainer = findViewById(R.id.search_button_container);
4019 final View searchButton = (ImageView) findViewById(R.id.search_button);
4020 updateButtonWithDrawable(R.id.search_button, d);
4021 invalidatePressedFocusedStates(searchButtonContainer, searchButton);
4022 }
4023
4024 protected boolean updateVoiceSearchIcon(boolean searchVisible) {
4025 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4026 final View voiceButton = findViewById(R.id.voice_button);
4027
4028 // We only show/update the voice search icon if the search icon is enabled as well
4029 final SearchManager searchManager =
4030 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
4031 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
4032
4033 ComponentName activityName = null;
4034 if (globalSearchActivity != null) {
4035 // Check if the global search activity handles voice search
4036 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
4037 intent.setPackage(globalSearchActivity.getPackageName());
4038 activityName = intent.resolveActivity(getPackageManager());
4039 }
4040
4041 if (activityName == null) {
4042 // Fallback: check if an activity other than the global search activity
4043 // resolves this
4044 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
4045 activityName = intent.resolveActivity(getPackageManager());
4046 }
4047 if (searchVisible && activityName != null) {
4048 int coi = getCurrentOrientationIndexForGlobalIcons();
4049 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4050 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
4051 TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
4052 if (sVoiceSearchIcon[coi] == null) {
4053 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4054 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
4055 TOOLBAR_ICON_METADATA_NAME);
4056 }
4057 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
4058 voiceButton.setVisibility(View.VISIBLE);
4059 updateVoiceButtonProxyVisible(false);
4060 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
4061 return true;
4062 } else {
4063 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
4064 if (voiceButton != null) voiceButton.setVisibility(View.GONE);
4065 updateVoiceButtonProxyVisible(false);
4066 return false;
4067 }
4068 }
4069
4070 protected void updateVoiceSearchIcon(Drawable.ConstantState d) {
4071 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4072 final View voiceButton = findViewById(R.id.voice_button);
4073 updateButtonWithDrawable(R.id.voice_button, d);
4074 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
4075 }
4076
4077 public void updateVoiceButtonProxyVisible(boolean forceDisableVoiceButtonProxy) {
4078 final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
4079 if (voiceButtonProxy != null) {
4080 boolean visible = !forceDisableVoiceButtonProxy &&
4081 mWorkspace.shouldVoiceButtonProxyBeVisible();
4082 voiceButtonProxy.setVisibility(visible ? View.VISIBLE : View.GONE);
4083 voiceButtonProxy.bringToFront();
4084 }
4085 }
4086
4087 /**
4088 * This is an overrid eot disable the voice button proxy. If disabled is true, then the voice button🔵
4089 * will be hidden regardless of what shouldVoiceButtonProxyBeVisible() returns.
4090 */
4091 public void disableVoiceButtonProxy(boolean disabled) {
4092 updateVoiceButtonProxyVisible(disabled);
4093 }
4094
4095 @Override
4096 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
4097 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
4098 final List<CharSequence> text = event.getText();
4099 text.clear();
4100 // Populate event with a fake title based on the current state.
4101 if (mState == State.APPS_CUSTOMIZE) {
4102 text.add(mAppsCustomizeTabHost.getContentTag());
4103 } else {
4104 text.add(getString(R.string.all_apps_home_button_label));
4105 }
4106 return result;
4107 }
4108
4109 /**
4110 * Receives notifications when system dialogs are to be closed.
4111 */
4112 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
4113 @Override
4114 public void onReceive(Context context, Intent intent) {
4115 closeSystemDialogs();
4116 }
4117 }
4118
4119 /**
4120 * Receives notifications whenever the appwidgets are reset.
4121 */
4122 private class AppWidgetResetObserver extends ContentObserver {
4123 public AppWidgetResetObserver() {
4124 super(new Handler());
4125 }
4126
4127 @Override
4128 public void onChange(boolean selfChange) {
4129 onAppWidgetReset();
4130 }
4131 }
4132
4133 /**
4134 * If the activity is currently paused, signal that we need to run the passed Runnable
4135 * in onResume.
4136 *
4137 * This needs to be called from incoming places where resources might have been loaded
4138 * while we are paused. That is becaues the Configuration might be wrong
4139 * when we're not running, and if it comes back to what it was when we
4140 * were paused, we are not restarted.
4141 *
4142 * Implementation of the method from LauncherModel.Callbacks.
4143 *
4144 * @return true if we are currently paused. The caller might be able to
4145 * skip some work in that case since we will come back again.
4146 */
4147 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
4148 if (mPaused) {
4149 Log.i(TAG, "Deferring update until onResume");
4150 if (deletePreviousRunnables) {
4151 while (mBindOnResumeCallbacks.remove(run)) {
4152 }
4153 }
4154 mBindOnResumeCallbacks.add(run);
4155 return true;
4156 } else {
4157 return false;
4158 }
4159 }
4160
4161 private boolean waitUntilResume(Runnable run) {
4162 return waitUntilResume(run, false);
4163 }
4164
4165 public void addOnResumeCallback(Runnable run) {
4166 mOnResumeCallbacks.add(run);
4167 }
4168
4169 /**
4170 * If the activity is currently paused, signal that we need to re-run the loader
4171 * in onResume.
4172 *
4173 * This needs to be called from incoming places where resources might have been loaded
4174 * while we are paused. That is becaues the Configuration might be wrong
4175 * when we're not running, and if it comes back to what it was when we
4176 * were paused, we are not restarted.
4177 *
4178 * Implementation of the method from LauncherModel.Callbacks.
4179 *
4180 * @return true if we are currently paused. The caller might be able to
4181 * skip some work in that case since we will come back again.
4182 */
4183 public boolean setLoadOnResume() {
4184 if (mPaused) {
4185 Log.i(TAG, "setLoadOnResume");
4186 mOnResumeNeedsLoad = true;
4187 return true;
4188 } else {
4189 return false;
4190 }
4191 }
4192
4193 /**
4194 * Implementation of the method from LauncherModel.Callbacks.
4195 */
4196 public int getCurrentWorkspaceScreen() {
4197 if (mWorkspace != null) {
4198 return mWorkspace.getCurrentPage();
4199 } else {
4200 return SCREEN_COUNT / 2;
4201 }
4202 }
4203
4204 /**
4205 * Refreshes the shortcuts shown on the workspace.
4206 *
4207 * Implementation of the method from LauncherModel.Callbacks.
4208 */
4209 public void startBinding() {
4210 setWorkspaceLoading(true);
4211
4212 // If we're starting binding all over again, clear any bind calls we'd postponed in
4213 // the past (see waitUntilResume) -- we don't need them since we're starting binding
4214 // from scratch again
4215 mBindOnResumeCallbacks.clear();
4216
4217 // Clear the workspace because it's going to be rebound
4218 mWorkspace.clearDropTargets();
4219 mWorkspace.removeAllWorkspaceScreens();
4220
4221 mWidgetsToAdvance.clear();
4222 if (mHotseat != null) {
4223 mHotseat.resetLayout();
4224 }
4225 }
4226
4227 @Override
4228 public void bindScreens(ArrayList<Long> orderedScreenIds) {
4229 bindAddScreens(orderedScreenIds);
4230
4231 // If there are no screens, we need to have an empty screen
4232 if (orderedScreenIds.size() == 0) {
4233 mWorkspace.addExtraEmptyScreen();
4234 }
4235
4236 // Create the custom content page (this call updates mDefaultScreen which calls
4237 // setCurrentPage() so ensure that all pages are added before calling this).
4238 if (hasCustomContentToLeft()) {
4239 mWorkspace.createCustomContentContainer();
4240 populateCustomContentContainer();
4241 }
4242 }
4243
4244 @Override
4245 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
4246 // Log to disk
4247 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
4248 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
4249 TextUtils.join(", ", orderedScreenIds), true);
4250 int count = orderedScreenIds.size();
4251 for (int i = 0; i < count; i++) {
4252 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
4253 }
4254 }
4255
4256 private boolean shouldShowWeightWatcher() {
4257 String spKey = LauncherAppState.getSharedPreferencesKey();
4258 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4259 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
4260
4261 return show;
4262 }
4263
4264 private void toggleShowWeightWatcher() {
4265 String spKey = LauncherAppState.getSharedPreferencesKey();
4266 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4267 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
4268
4269 show = !show;
4270
4271 SharedPreferences.Editor editor = sp.edit();
4272 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
4273 editor.commit();
4274
4275 if (mWeightWatcher != null) {
4276 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
4277 }
4278 }
4279
4280 public void bindAppsAdded(final ArrayList<Long> newScreens,
4281 final ArrayList<ItemInfo> addNotAnimated,
4282 final ArrayList<ItemInfo> addAnimated,
4283 final ArrayList<AppInfo> addedApps) {
4284 Runnable r = new Runnable() {
4285 public void run() {
4286 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
4287 }
4288 };
4289 if (waitUntilResume(r)) {
4290 return;
4291 }
4292
4293 // Add the new screens
4294 if (newScreens != null) {
4295 bindAddScreens(newScreens);
4296 }
4297
4298 // We add the items without animation on non-visible pages, and with
4299 // animations on the new page (which we will try and snap to).
4300 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
4301 bindItems(addNotAnimated, 0,
4302 addNotAnimated.size(), false);
4303 }
4304 if (addAnimated != null && !addAnimated.isEmpty()) {
4305 bindItems(addAnimated, 0,
4306 addAnimated.size(), true);
4307 }
4308
4309 // Remove the extra empty screen
4310 mWorkspace.removeExtraEmptyScreen(false, false);
4311
4312 if (!LauncherAppState.isDisableAllApps() &&
4313 addedApps != null && mAppsCustomizeContent != null) {
4314 mAppsCustomizeContent.addApps(addedApps);
4315 }
4316 }
4317
4318 /**
4319 * Bind the items start-end from the list.
4320 *
4321 * Implementation of the method from LauncherModel.Callbacks.
4322 */
4323 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
4324 final boolean forceAnimateIcons) {
4325 Runnable r = new Runnable() {
4326 public void run() {
4327 bindItems(shortcuts, start, end, forceAnimateIcons);
4328 }
4329 };
4330 if (waitUntilResume(r)) {
4331 return;
4332 }
4333
4334 // Get the list of added shortcuts and intersect them with the set of shortcuts here
4335 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
4336 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
4337 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
4338 Workspace workspace = mWorkspace;
4339 long newShortcutsScreenId = -1;
4340 for (int i = start; i < end; i++) {
4341 final ItemInfo item = shortcuts.get(i);
4342
4343 // Short circuit if we are loading dock items for a configuration which has no dock
4344 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
4345 mHotseat == null) {
4346 continue;
4347 }
4348
4349 switch (item.itemType) {
4350 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
4351 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
4352 ShortcutInfo info = (ShortcutInfo) item;
4353 View shortcut = createShortcut(info);
4354
4355 /*
4356 * TODO: FIX collision case
4357 */
4358 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
4359 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
4360 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
4361 View v = cl.getChildAt(item.cellX, item.cellY);
4362 Object tag = v.getTag();
4363 String desc = "Collision while binding workspace item: " + item
4364 + ". Collides with " + tag;
4365 if (LauncherAppState.isDogfoodBuild()) {
4366 throw (new RuntimeException(desc));
4367 } else {
4368 Log.d(TAG, desc);
4369 }
4370 }
4371 }
4372
4373 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
4374 item.cellY, 1, 1);
4375 if (animateIcons) {
4376 // Animate all the applications up now
4377 shortcut.setAlpha(0f);
4378 shortcut.setScaleX(0f);
4379 shortcut.setScaleY(0f);
4380 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
4381 newShortcutsScreenId = item.screenId;
4382 }
4383 break;
4384 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
4385 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
4386 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
4387 (FolderInfo) item, mIconCache);
4388 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
4389 item.cellY, 1, 1);
4390 break;
4391 default:
4392 throw new RuntimeException("Invalid Item Type");
4393 }
4394 }
4395
4396 if (animateIcons) {
4397 // Animate to the correct page
4398 if (newShortcutsScreenId > -1) {
4399 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
4400 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
4401 final Runnable startBounceAnimRunnable = new Runnable() {
4402 public void run() {
4403 anim.playTogether(bounceAnims);
4404 anim.start();
4405 }
4406 };
4407 if (newShortcutsScreenId != currentScreenId) {
4408 // We post the animation slightly delayed to prevent slowdowns
4409 // when we are loading right after we return to launcher.
4410 mWorkspace.postDelayed(new Runnable() {
4411 public void run() {
4412 if (mWorkspace != null) {
4413 mWorkspace.snapToPage(newScreenIndex);
4414 mWorkspace.postDelayed(startBounceAnimRunnable,
4415 NEW_APPS_ANIMATION_DELAY);
4416 }
4417 }
4418 }, NEW_APPS_PAGE_MOVE_DELAY);
4419 } else {
4420 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
4421 }
4422 }
4423 }
4424 workspace.requestLayout();
4425 }
4426
4427 /**
4428 * Implementation of the method from LauncherModel.Callbacks.
4429 */
4430 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
4431 Runnable r = new Runnable() {
4432 public void run() {
4433 bindFolders(folders);
4434 }
4435 };
4436 if (waitUntilResume(r)) {
4437 return;
4438 }
4439 sFolders.clear();
4440 sFolders.putAll(folders);
4441 }
4442
4443 /**
4444 * Add the views for a widget to the workspace.
4445 *
4446 * Implementation of the method from LauncherModel.Callbacks.
4447 */
4448 public void bindAppWidget(final LauncherAppWidgetInfo item) {
4449 Runnable r = new Runnable() {
4450 public void run() {
4451 bindAppWidget(item);
4452 }
4453 };
4454 if (waitUntilResume(r)) {
4455 return;
4456 }
4457
4458 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
4459 if (DEBUG_WIDGETS) {
4460 Log.d(TAG, "bindAppWidget: " + item);
4461 }
4462 final Workspace workspace = mWorkspace;
4463
4464 AppWidgetProviderInfo appWidgetInfo;
4465 if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) &&
4466 ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
4467
4468 appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);
4469 if (appWidgetInfo == null) {
4470 if (DEBUG_WIDGETS) {
4471 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4472 + " belongs to component " + item.providerName
4473 + ", as the povider is null");
4474 }
4475 LauncherModel.deleteItemFromDatabase(this, item);
4476 return;
4477 }
4478 // Note: This assumes that the id remap broadcast is received before this step.
4479 // If that is not the case, the id remap will be ignored and user may see the
4480 // click to setup view.
4481 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);
4482 pendingInfo.spanX = item.spanX;
4483 pendingInfo.spanY = item.spanY;
4484 pendingInfo.minSpanX = item.minSpanX;
4485 pendingInfo.minSpanY = item.minSpanY;
4486 Bundle options =
4487 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
4488
4489 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
4490 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
4491 newWidgetId, appWidgetInfo, options);
4492
4493 // TODO consider showing a permission dialog when the widget is clicked.
4494 if (!success) {
4495 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
4496 if (DEBUG_WIDGETS) {
4497 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4498 + " belongs to component " + item.providerName
4499 + ", as the launcher is unable to bing a new widget id");
4500 }
4501 LauncherModel.deleteItemFromDatabase(this, item);
4502 return;
4503 }
4504
4505 item.appWidgetId = newWidgetId;
4506
4507 // If the widget has a configure activity, it is still needs to set it up, otherwise
4508 // the widget is ready to go.
4509 item.restoreStatus = (appWidgetInfo.configure == null)
4510 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4511 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4512
4513 LauncherModel.updateItemInDatabase(this, item);
4514 }
4515
4516 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4517 final int appWidgetId = item.appWidgetId;
4518 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
4519 if (DEBUG_WIDGETS) {
4520 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidget🔵
4521 }
4522
4523 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4524 } else {
4525 appWidgetInfo = null;
4526 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item);
4527 view.updateIcon(mIconCache);
4528 item.hostView = view;
4529 item.hostView.updateAppWidget(null);
4530 item.hostView.setOnClickListener(this);
4531 }
4532
4533 item.hostView.setTag(item);
4534 item.onBindAppWidget(this);
4535
4536 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4537 item.cellY, item.spanX, item.spanY, false);
4538 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4539
4540 workspace.requestLayout();
4541
4542 if (DEBUG_WIDGETS) {
4543 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4544 + (SystemClock.uptimeMillis()-start) + "ms");
4545 }
4546 }
4547
4548 /**
4549 * Restores a pending widget.
4550 *
4551 * @param appWidgetId The app widget id
4552 * @param cellInfo The position on screen where to create the widget.
4553 */
4554 private void completeRestoreAppWidget(final int appWidgetId) {
4555 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4556 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4557 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4558 return;
4559 }
4560
4561 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4562 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4563
4564 mWorkspace.reinflateWidgetsIfNecessary();
4565 LauncherModel.updateItemInDatabase(this, info);
4566 }
4567
4568 public void onPageBoundSynchronously(int page) {
4569 mSynchronouslyBoundPages.add(page);
4570 }
4571
4572 /**
4573 * Callback saying that there aren't any more items to bind.
4574 *
4575 * Implementation of the method from LauncherModel.Callbacks.
4576 */
4577 public void finishBindingItems(final boolean upgradePath) {
4578 Runnable r = new Runnable() {
4579 public void run() {
4580 finishBindingItems(upgradePath);
4581 }
4582 };
4583 if (waitUntilResume(r)) {
4584 return;
4585 }
4586 if (mSavedState != null) {
4587 if (!mWorkspace.hasFocus()) {
4588 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4589 }
4590 mSavedState = null;
4591 }
4592
4593 mWorkspace.restoreInstanceStateForRemainingPages();
4594
4595 setWorkspaceLoading(false);
4596 sendLoadingCompleteBroadcastIfNecessary();
4597
4598 // If we received the result of any pending adds while the loader was running (e.g. the
4599 // widget configuration forced an orientation change), process them now.
4600 if (sPendingAddItem != null) {
4601 final long screenId = completeAdd(sPendingAddItem);
4602
4603 // TODO: this moves the user to the page where the pending item was added. Ideally,
4604 // the screen would be guaranteed to exist after bind, and the page would be set through
4605 // the workspace restore process.
4606 mWorkspace.post(new Runnable() {
4607 @Override
4608 public void run() {
4609 mWorkspace.snapToScreenId(screenId);
4610 }
4611 });
4612 sPendingAddItem = null;
4613 }
4614
4615 if (upgradePath) {
4616 mWorkspace.getUniqueComponents(true, null);
4617 mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
4618 }
4619 PackageInstallerCompat.getInstance(this).onFinishBind();
4620 mModel.recheckRestoredItems(this);
4621 }
4622
4623 private void sendLoadingCompleteBroadcastIfNecessary() {
4624 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4625 String permission =
4626 getResources().getString(R.string.receive_first_load_broadcast_permission);
4627 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4628 sendBroadcast(intent, permission);
4629 SharedPreferences.Editor editor = mSharedPrefs.edit();
4630 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4631 editor.apply();
4632 }
4633 }
4634
4635 public boolean isAllAppsButtonRank(int rank) {
4636 if (mHotseat != null) {
4637 return mHotseat.isAllAppsButtonRank(rank);
4638 }
4639 return false;
4640 }
4641
4642 private boolean canRunNewAppsAnimation() {
4643 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4644 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4645 }
4646
4647 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4648 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4649 PropertyValuesHolder.ofFloat("alpha", 1f),
4650 PropertyValuesHolder.ofFloat("scaleX", 1f),
4651 PropertyValuesHolder.ofFloat("scaleY", 1f));
4652 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4653 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4654 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4655 return bounceAnim;
4656 }
4657
4658 public boolean useVerticalBarLayout() {
4659 return LauncherAppState.getInstance().getDynamicGrid().
4660 getDeviceProfile().isVerticalBarLayout();
4661 }
4662
4663 protected Rect getSearchBarBounds() {
4664 return LauncherAppState.getInstance().getDynamicGrid().
4665 getDeviceProfile().getSearchBarBounds();
4666 }
4667
4668 @Override
4669 public void bindSearchablesChanged() {
4670 boolean searchVisible = updateGlobalSearchIcon();
4671 boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
4672 if (mSearchDropTargetBar != null) {
4673 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
4674 }
4675 }
4676
4677 /**
4678 * Add the icons for all apps.
4679 *
4680 * Implementation of the method from LauncherModel.Callbacks.
4681 */
4682 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4683 if (LauncherAppState.isDisableAllApps()) {
4684 if (mIntentsOnWorkspaceFromUpgradePath != null) {
4685 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
4686 getHotseat().addAllAppsFolder(mIconCache, apps,
4687 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
4688 }
4689 mIntentsOnWorkspaceFromUpgradePath = null;
4690 }
4691 if (mAppsCustomizeContent != null) {
4692 mAppsCustomizeContent.onPackagesUpdated(
4693 LauncherModel.getSortedWidgetsAndShortcuts(this));
4694 }
4695 } else {
4696 if (mAppsCustomizeContent != null) {
4697 mAppsCustomizeContent.setApps(apps);
4698 mAppsCustomizeContent.onPackagesUpdated(
4699 LauncherModel.getSortedWidgetsAndShortcuts(this));
4700 }
4701 }
4702 }
4703
4704 /**
4705 * A package was updated.
4706 *
4707 * Implementation of the method from LauncherModel.Callbacks.
4708 */
4709 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4710 Runnable r = new Runnable() {
4711 public void run() {
4712 bindAppsUpdated(apps);
4713 }
4714 };
4715 if (waitUntilResume(r)) {
4716 return;
4717 }
4718
4719 if (mWorkspace != null) {
4720 mWorkspace.updateShortcutsAndWidgets(apps);
4721 }
4722
4723 if (!LauncherAppState.isDisableAllApps() &&
4724 mAppsCustomizeContent != null) {
4725 mAppsCustomizeContent.updateApps(apps);
4726 }
4727 }
4728
4729 /**
4730 <<<<<<< GitAnalyzerPlus_ours
4731 * Some shortcuts were updated in the background.
4732 *
4733 * Implementation of the method from LauncherModel.Callbacks.
4734 */
4735 public void bindShortcutsUpdated(final ArrayList<ShortcutInfo> shortcuts) {
4736 Runnable r = new Runnable() {
4737 public void run() {
4738 bindShortcutsUpdated(shortcuts);
4739 }
4740 };
4741 if (waitUntilResume(r)) {
4742 return;
4743 }
4744
4745 if (mWorkspace != null) {
4746 mWorkspace.updateShortcuts(shortcuts);
4747 ||||||| GitAnalyzerPlus_base
4748 *
4749 * Implementation of the method from LauncherModel.Callbacks.
4750 */
4751 @Override
4752 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4753 if (mWorkspace != null) {
4754 mWorkspace.updatePackageState(installInfo);
4755 }
4756 }
4757
4758 /**
4759 * Update the label and icon of all the icons in a package
4760 *
4761 * Implementation of the method from LauncherModel.Callbacks.
4762 */
4763 @Override
4764 public void updatePackageBadge(String packageName) {
4765 if (mWorkspace != null) {
4766 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4767 }
4768 }
4769
4770 =======
4771 * Packages were restored
4772 */
4773 public void bindAppsRestored(final ArrayList<AppInfo> apps) {
4774 Runnable r = new Runnable() {
4775 public void run() {
4776 bindAppsRestored(apps);
4777 }
4778 };
4779 if (waitUntilResume(r)) {
4780 return;
4781 }
4782
4783 if (mWorkspace != null) {
4784 mWorkspace.updateShortcutsAndWidgets(apps);
4785 >>>>>>> GitAnalyzerPlus_theirs
4786 }
4787 }
4788
4789 /**
4790 * Update the state of a package, typically related to install state.
4791 *
4792 * Implementation of the method from LauncherModel.Callbacks.
4793 */
4794 @Override
4795 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4796 if (mWorkspace != null) {
4797 mWorkspace.updatePackageState(installInfo);
4798 }
4799 }
4800
4801 /**
4802 * Update the label and icon of all the icons in a package
4803 *
4804 * Implementation of the method from LauncherModel.Callbacks.
4805 */
4806 @Override
4807 public void updatePackageBadge(String packageName) {
4808 if (mWorkspace != null) {
4809 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4810 }
4811 }
4812
4813 /**
4814 * A package was uninstalled. We take both the super set of packageNames
4815 * in addition to specific applications to remove, the reason being that
4816 * this can be called when a package is updated as well. In that scenario,
4817 * we only remove specific components from the workspace, where as
4818 * package-removal should clear all items by package name.
4819 *
4820 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4821 * Implementation of the method from LauncherModel.Callbacks.
4822 */
4823 @Override
4824 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4825 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4826 Runnable r = new Runnable() {
4827 public void run() {
4828 bindComponentsRemoved(packageNames, appInfos, user, reason);
4829 }
4830 };
4831 if (waitUntilResume(r)) {
4832 return;
4833 }
4834
4835 if (reason == 0) {
4836 if (!packageNames.isEmpty()) {
4837 mWorkspace.removeItemsByPackageName(packageNames, user);
4838 }
4839 if (!appInfos.isEmpty()) {
4840 mWorkspace.removeItemsByApplicationInfo(appInfos, user);
4841 }
4842 } else {
4843 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4844 }
4845
4846 // Notify the drag controller
4847 mDragController.onAppsRemoved(packageNames, appInfos);
4848
4849 // Update AllApps
4850 if (!LauncherAppState.isDisableAllApps() &&
4851 mAppsCustomizeContent != null) {
4852 mAppsCustomizeContent.removeApps(appInfos);
4853 }
4854 }
4855
4856 /**
4857 * A number of packages were updated.
4858 */
4859 private ArrayList<Object> mWidgetsAndShortcuts;
4860 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4861 public void run() {
4862 bindPackagesUpdated(mWidgetsAndShortcuts);
4863 mWidgetsAndShortcuts = null;
4864 }
4865 };
4866 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4867 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4868 mWidgetsAndShortcuts = widgetsAndShortcuts;
4869 return;
4870 }
4871
4872 // Update the widgets pane
4873 if (mAppsCustomizeContent != null) {
4874 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
4875 }
4876 }
4877
4878 private int mapConfigurationOriActivityInfoOri(int configOri) {
4879 final Display d = getWindowManager().getDefaultDisplay();
4880 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4881 switch (d.getRotation()) {
4882 case Surface.ROTATION_0:
4883 case Surface.ROTATION_180:
4884 // We are currently in the same basic orientation as the natural orientation
4885 naturalOri = configOri;
4886 break;
4887 case Surface.ROTATION_90:
4888 case Surface.ROTATION_270:
4889 // We are currently in the other basic orientation to the natural orientation
4890 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4891 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4892 break;
4893 }
4894
4895 int[] oriMap = {
4896 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4897 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4898 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4899 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4900 };
4901 // Since the map starts at portrait, we need to offset if this device's natural orientation
4902 // is landscape.
4903 int indexOffset = 0;
4904 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4905 indexOffset = 1;
4906 }
4907 return oriMap[(d.getRotation() + indexOffset) % 4];
4908 }
4909
4910 public boolean isRotationEnabled() {
4911 boolean enableRotation = sForceEnableRotation ||
4912 getResources().getBoolean(R.bool.allow_rotation);
4913 return enableRotation;
4914 }
4915 public void lockScreenOrientation() {
4916 if (isRotationEnabled()) {
4917 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4918 .getConfiguration().orientation));
4919 }
4920 }
4921 public void unlockScreenOrientation(boolean immediate) {
4922 if (isRotationEnabled()) {
4923 if (immediate) {
4924 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4925 } else {
4926 mHandler.postDelayed(new Runnable() {
4927 public void run() {
4928 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4929 }
4930 }, mRestoreScreenOrientationDelay);
4931 }
4932 }
4933 }
4934
4935 /**
4936 * Called when the SearchBar hint should be changed.
4937 *
4938 * @param hint the hint to be displayed in the search bar.
4939 */
4940 protected void onSearchBarHintChanged(String hint) {
4941
4942 }
4943
4944 protected boolean isLauncherPreinstalled() {
4945 PackageManager pm = getPackageManager();
4946 try {
4947 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4948 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4949 return true;
4950 } else {
4951 return false;
4952 }
4953 } catch (NameNotFoundException e) {
4954 e.printStackTrace();
4955 return false;
4956 }
4957 }
4958
4959 /**
4960 * This method indicates whether or not we should suggest default wallpaper dimensions
4961 * when our wallpaper cropper was not yet used to set a wallpaper.
4962 */
4963 protected boolean overrideWallpaperDimensions() {
4964 return true;
4965 }
4966
4967 /**
4968 * To be overridden by subclasses to indicate that there is an activity to launch
4969 * before showing the standard launcher experience.
4970 */
4971 protected boolean hasFirstRunActivity() {
4972 return false;
4973 }
4974
4975 /**
4976 * To be overridden by subclasses to launch any first run activity
4977 */
4978 protected Intent getFirstRunActivity() {
4979 return null;
4980 }
4981
4982 private boolean shouldRunFirstRunActivity() {
4983 return !ActivityManager.isRunningInTestHarness() &&
4984 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4985 }
4986
4987 protected boolean hasRunFirstRunActivity() {
4988 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4989 }
4990
4991 public boolean showFirstRunActivity() {
4992 if (shouldRunFirstRunActivity() &&
4993 hasFirstRunActivity()) {
4994 Intent firstRunIntent = getFirstRunActivity();
4995 if (firstRunIntent != null) {
4996 startActivity(firstRunIntent);
4997 markFirstRunActivityShown();
4998 return true;
4999 }
5000 }
5001 return false;
5002 }
5003
5004 private void markFirstRunActivityShown() {
5005 SharedPreferences.Editor editor = mSharedPrefs.edit();
5006 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
5007 editor.apply();
5008 }
5009
5010 /**
5011 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
5012 * screen that must be displayed and dismissed.
5013 */
5014 protected boolean hasDismissableIntroScreen() {
5015 return false;
5016 }
5017
5018 /**
5019 * Full screen intro screen to be shown and dismissed before the launcher can be used.
5020 */
5021 protected View getIntroScreen() {
5022 return null;
5023 }
5024
5025 /**
5026 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
5027 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
5028 */
5029 private boolean shouldShowIntroScreen() {
5030 return hasDismissableIntroScreen() &&
5031 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
5032 }
5033
5034 protected void showIntroScreen() {
5035 View introScreen = getIntroScreen();
5036 changeWallpaperVisiblity(false);
5037 if (introScreen != null) {
5038 mDragLayer.showOverlayView(introScreen);
5039 }
5040 }
5041
5042 public void dismissIntroScreen() {
5043 markIntroScreenDismissed();
5044 if (showFirstRunActivity()) {
5045 // We delay hiding the intro view until the first run activity is showing. This
5046 // avoids a blip.
5047 mWorkspace.postDelayed(new Runnable() {
5048 @Override
5049 public void run() {
5050 mDragLayer.dismissOverlayView();
5051 showFirstRunClings();
5052 }
5053 }, ACTIVITY_START_DELAY);
5054 } else {
5055 mDragLayer.dismissOverlayView();
5056 showFirstRunClings();
5057 }
5058 changeWallpaperVisiblity(true);
5059 }
5060
5061 private void markIntroScreenDismissed() {
5062 SharedPreferences.Editor editor = mSharedPrefs.edit();
5063 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
5064 editor.apply();
5065 }
5066
5067 private void showFirstRunClings() {
5068 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
5069 // on the device, then we always show the first run cling experience (or if there is no
5070 // launcher2). Otherwise, we prompt the user upon started for migration
5071 LauncherClings launcherClings = new LauncherClings(this);
5072 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
5073 if (mModel.canMigrateFromOldLauncherDb(this)) {
5074 launcherClings.showMigrationCling();
5075 } else {
5076 launcherClings.showLongPressCling(true);
5077 }
5078 }
5079 }
5080
5081 void showWorkspaceSearchAndHotseat() {
5082 if (mWorkspace != null) mWorkspace.setAlpha(1f);
5083 if (mHotseat != null) mHotseat.setAlpha(1f);
5084 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
5085 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
5086 }
5087
5088 void hideWorkspaceSearchAndHotseat() {
5089 if (mWorkspace != null) mWorkspace.setAlpha(0f);
5090 if (mHotseat != null) mHotseat.setAlpha(0f);
5091 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
5092 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
5093 }
5094
5095 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
5096 // Called from search suggestion, not supported in other profiles.
5097 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
5098 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
5099 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
5100 myUser);
5101 if (activityInfo == null) {
5102 return null;
5103 }
5104 return new AppInfo(this, activityInfo, myUser, mIconCache, null);
5105 }
5106
5107 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5108 Bitmap icon) {
5109 // Called from search suggestion, not supported in other profiles.
5110 return createShortcutDragInfo(shortcutIntent, caption, icon,
5111 UserHandleCompat.myUserHandle());
5112 }
5113
5114 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5115 Bitmap icon, UserHandleCompat user) {
5116 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
5117 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
5118 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
5119 }
5120
5121 protected void moveWorkspaceToDefaultScreen() {
5122 mWorkspace.moveToDefaultScreen(false);
5123 }
5124
5125 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
5126 dragView.setTag(dragInfo);
5127 mWorkspace.onExternalDragStartedWithItem(dragView);
5128 mWorkspace.beginExternalDragShared(dragView, source);
5129 }
5130
5131 @Override
5132 public void onPageSwitch(View newPage, int newPageIndex) {
5133 }
5134
5135 /**
5136 * Prints out out state for debugging.
5137 */
5138 public void dumpState() {
5139 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
5140 Log.d(TAG, "mSavedState=" + mSavedState);
5141 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
5142 Log.d(TAG, "mRestoring=" + mRestoring);
5143 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
5144 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
5145 Log.d(TAG, "sFolders.size=" + sFolders.size());
5146 mModel.dumpState();
5147
5148 if (mAppsCustomizeContent != null) {
5149 mAppsCustomizeContent.dumpState();
5150 }
5151 Log.d(TAG, "END launcher3 dump state");
5152 }
5153
5154 @Override
5155 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
5156 super.dump(prefix, fd, writer, args);
5157 synchronized (sDumpLogs) {
5158 writer.println(" ");
5159 writer.println("Debug logs: ");
5160 for (int i = 0; i < sDumpLogs.size(); i++) {
5161 writer.println(" " + sDumpLogs.get(i));
5162 }
5163 }
5164 }
5165
5166 public static void dumpDebugLogsToConsole() {
5167 if (DEBUG_DUMP_LOG) {
5168 synchronized (sDumpLogs) {
5169 Log.d(TAG, "");
5170 Log.d(TAG, "*********************");
5171 Log.d(TAG, "Launcher debug logs: ");
5172 for (int i = 0; i < sDumpLogs.size(); i++) {
5173 Log.d(TAG, " " + sDumpLogs.get(i));
5174 }
5175 Log.d(TAG, "*********************");
5176 Log.d(TAG, "");
5177 }
5178 }
5179 }
5180
5181 public static void addDumpLog(String tag, String log, boolean debugLog) {
5182 addDumpLog(tag, log, null, debugLog);
5183 }
5184
5185 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
5186 if (debugLog) {
5187 if (e != null) {
5188 Log.d(tag, log, e);
5189 } else {
5190 Log.d(tag, log);
5191 }
5192 }
5193 if (DEBUG_DUMP_LOG) {
5194 sDateStamp.setTime(System.currentTimeMillis());
5195 synchronized (sDumpLogs) {
5196 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
5197 + (e == null ? "" : (", Exception: " + e)));
5198 }
5199 }
5200 }
5201
5202 public void dumpLogsToLocalData() {
5203 if (DEBUG_DUMP_LOG) {
5204 new AsyncTask<Void, Void, Void>() {
5205 public Void doInBackground(Void ... args) {
5206 boolean success = false;
5207 sDateStamp.setTime(sRunStart);
5208 String FILENAME = sDateStamp.getMonth() + "-"
5209 + sDateStamp.getDay() + "_"
5210 + sDateStamp.getHours() + "-"
5211 + sDateStamp.getMinutes() + "_"
5212 + sDateStamp.getSeconds() + ".txt";
5213
5214 FileOutputStream fos = null;
5215 File outFile = null;
5216 try {
5217 outFile = new File(getFilesDir(), FILENAME);
5218 outFile.createNewFile();
5219 fos = new FileOutputStream(outFile);
5220 } catch (Exception e) {
5221 e.printStackTrace();
5222 }
5223 if (fos != null) {
5224 PrintWriter writer = new PrintWriter(fos);
5225
5226 writer.println(" ");
5227 writer.println("Debug logs: ");
5228 synchronized (sDumpLogs) {
5229 for (int i = 0; i < sDumpLogs.size(); i++) {
5230 writer.println(" " + sDumpLogs.get(i));
5231 }
5232 }
5233 writer.close();
5234 }
5235 try {
5236 if (fos != null) {
5237 fos.close();
5238 success = true;
5239 }
5240 } catch (IOException e) {
5241 e.printStackTrace();
5242 }
5243 return null;
5244 }
5245 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
5246 }
5247 }
5248 }
5249
5250 interface LauncherTransitionable {
5251 View getContent();
5252 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
5253 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
5254 void onLauncherTransitionStep(Launcher l, float t);
5255 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
5256 }
5257
5258 interface DebugIntents {
5259 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
5260 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
5261 }
|
1
2 /*
3 * Copyright (C) 2008 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package com.android.launcher3;
19
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator;
27 import android.annotation.TargetApi;
28 import android.app.Activity;
29 import android.app.ActivityManager;
30 import android.app.ActivityOptions;
31 import android.app.AlertDialog;
32 import android.app.SearchManager;
33 import android.appwidget.AppWidgetHostView;
34 import android.appwidget.AppWidgetManager;
35 import android.appwidget.AppWidgetProviderInfo;
36 import android.content.ActivityNotFoundException;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentCallbacks2;
39 import android.content.ComponentName;
40 import android.content.ContentResolver;
41 import android.content.Context;
42 import android.content.DialogInterface;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.content.SharedPreferences;
46 import android.content.pm.ActivityInfo;
47 import android.content.pm.ApplicationInfo;
48 import android.content.pm.PackageManager;
49 import android.content.pm.PackageManager.NameNotFoundException;
50 import android.content.res.Configuration;
51 import android.content.res.Resources;
52 import android.database.ContentObserver;
53 import android.graphics.Bitmap;
54 import android.graphics.Canvas;
55 import android.graphics.Color;
56 import android.graphics.Point;
57 import android.graphics.PorterDuff;
58 import android.graphics.Rect;
59 import android.graphics.drawable.Drawable;
60 import android.net.Uri;
61 import android.os.AsyncTask;
62 import android.os.Build;
63 import android.os.Bundle;
64 import android.os.Environment;
65 import android.os.Handler;
66 import android.os.Message;
67 import android.os.StrictMode;
68 import android.os.SystemClock;
69 import android.speech.RecognizerIntent;
70 import android.text.Selection;
71 import android.text.SpannableStringBuilder;
72 import android.text.TextUtils;
73 import android.text.method.TextKeyListener;
74 import android.util.DisplayMetrics;
75 import android.util.Log;
76 import android.view.Display;
77 import android.view.Gravity;
78 import android.view.HapticFeedbackConstants;
79 import android.view.KeyEvent;
80 import android.view.LayoutInflater;
81 import android.view.Menu;
82 import android.view.MotionEvent;
83 import android.view.Surface;
84 import android.view.View;
85 import android.view.View.OnClickListener;
86 import android.view.View.OnLongClickListener;
87 import android.view.ViewAnimationUtils;
88 import android.view.ViewGroup;
89 import android.view.ViewTreeObserver;
90 import android.view.Window;
91 import android.view.WindowManager;
92 import android.view.accessibility.AccessibilityEvent;
93 import android.view.animation.AccelerateInterpolator;
94 import android.view.animation.DecelerateInterpolator;
95 import android.view.inputmethod.InputMethodManager;
96 import android.widget.Advanceable;
97 import android.widget.FrameLayout;
98 import android.widget.ImageView;
99 import android.widget.TextView;
100 import android.widget.Toast;
101
102 import com.android.launcher3.DropTarget.DragObject;
103 import com.android.launcher3.PagedView.PageSwitchListener;
104 import com.android.launcher3.compat.AppWidgetManagerCompat;
105 import com.android.launcher3.compat.LauncherActivityInfoCompat;
106 import com.android.launcher3.compat.LauncherAppsCompat;
107 import com.android.launcher3.compat.PackageInstallerCompat;
108 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
109 import com.android.launcher3.compat.UserHandleCompat;
110 import com.android.launcher3.compat.UserManagerCompat;
111
112 import java.io.DataInputStream;
113 import java.io.DataOutputStream;
114 import java.io.File;
115 import java.io.FileDescriptor;
116 import java.io.FileNotFoundException;
117 import java.io.FileOutputStream;
118 import java.io.IOException;
119 import java.io.PrintWriter;
120 import java.lang.reflect.Field;
121 import java.lang.reflect.InvocationTargetException;
122 import java.lang.reflect.Method;
123 import java.text.DateFormat;
124 import java.util.ArrayList;
125 import java.util.Collection;
126 import java.util.Date;
127 import java.util.HashMap;
128 import java.util.List;
129 import java.util.concurrent.atomic.AtomicInteger;
130
131 /**
132 * Default launcher application.
133 */
134 public class Launcher extends Activity
135 implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
136 View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
137 static final String TAG = "Launcher";
138 static final boolean LOGD = false;
139
140 static final boolean PROFILE_STARTUP = false;
141 static final boolean DEBUG_WIDGETS = false;
142 static final boolean DEBUG_STRICT_MODE = false;
143 static final boolean DEBUG_RESUME_TIME = false;
144 static final boolean DEBUG_DUMP_LOG = false;
145
146 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
147
148 private static final int REQUEST_CREATE_SHORTCUT = 1;
149 private static final int REQUEST_CREATE_APPWIDGET = 5;
150 private static final int REQUEST_PICK_SHORTCUT = 7;
151 private static final int REQUEST_PICK_APPWIDGET = 9;
152 private static final int REQUEST_PICK_WALLPAPER = 10;
153
154 private static final int REQUEST_BIND_APPWIDGET = 11;
155 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
156
157 /**
158 * IntentStarter uses request codes starting with this. This must be greater than all activity
159 * request codes used internally.
160 */
161 protected static final int REQUEST_LAST = 100;
162
163 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
164
165 static final int SCREEN_COUNT = 5;
166 static final int DEFAULT_SCREEN = 2;
167
168 // To turn on these properties, type
169 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
170 static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
171 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
172 static final String DISABLE_ALL_APPS_PROPERTY = "launcher_noallapps";
173
174 // The Intent extra that defines whether to ignore the launch animation
175 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
176 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
177
178 // Type: int
179 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
180 // Type: int
181 private static final String RUNTIME_STATE = "launcher.state";
182 // Type: int
183 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
184 // Type: int
185 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
186 // Type: int
187 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
188 // Type: int
189 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
190 // Type: boolean
191 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
192 // Type: long
193 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
194 // Type: int
195 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
196 // Type: int
197 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
198 // Type: parcelable
199 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
200 // Type: parcelable
201 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
202 // Type: int[]
203 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
204
205 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
206 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
207
208 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
209 static final String ACTION_FIRST_LOAD_COMPLETE =
210 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
211
212 private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
213 private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
214 "com.android.launcher.toolbar_search_icon";
215 private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
216 "com.android.launcher.toolbar_voice_search_icon";
217
218 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
219 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
220
221 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
222
223 /** The different states that Launcher can be in. */
224 private enum State { NONE, WORKSPACE, APPS_CUSTOMIZE, APPS_CUSTOMIZE_SPRING_LOADED };;
225 private State mState = State.WORKSPACE;
226 private AnimatorSet mStateAnimation;
227
228 private boolean mIsSafeModeEnabled;
229
230 static final int APPWIDGET_HOST_ID = 1024;
231 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
232 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
233 private static final int ACTIVITY_START_DELAY = 1000;
234
235 private static final Object sLock = new Object();
236 private static int sScreen = DEFAULT_SCREEN;
237
238 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
239 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
240
241 // How long to wait before the new-shortcut animation automatically pans the workspace
242 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
243 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
244 private static int NEW_APPS_ANIMATION_DELAY = 500;
245 private static final int SINGLE_FRAME_DELAY = 16;
246
247 private final BroadcastReceiver mCloseSystemDialogsReceiver
248 = new CloseSystemDialogsIntentReceiver();
249 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
250
251 private LayoutInflater mInflater;
252
253 private Workspace mWorkspace;
254 private View mLauncherView;
255 private View mPageIndicators;
256 private DragLayer mDragLayer;
257 private DragController mDragController;
258 private View mWeightWatcher;
259
260 private AppWidgetManagerCompat mAppWidgetManager;
261 private LauncherAppWidgetHost mAppWidgetHost;
262
263 private ItemInfo mPendingAddInfo = new ItemInfo();
264 private AppWidgetProviderInfo mPendingAddWidgetInfo;
265 private int mPendingAddWidgetId = -1;
266
267 private int[] mTmpAddItemCellCoordinates = new int[2];
268
269 private FolderInfo mFolderInfo;
270
271 private Hotseat mHotseat;
272 private ViewGroup mOverviewPanel;
273
274 private View mAllAppsButton;
275
276 private SearchDropTargetBar mSearchDropTargetBar;
277 private AppsCustomizeTabHost mAppsCustomizeTabHost;
278 private AppsCustomizePagedView mAppsCustomizeContent;
279 private boolean mAutoAdvanceRunning = false;
280 private View mQsb;
281
282 private Bundle mSavedState;
283 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
284 // scroll issues (because the workspace may not have been measured yet) and extra work.
285 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
286 private State mOnResumeState = State.NONE;
287
288 private SpannableStringBuilder mDefaultKeySsb = null;
289
290 private boolean mWorkspaceLoading = true;
291
292 private boolean mPaused = true;
293 private boolean mRestoring;
294 private boolean mWaitingForResult;
295 private boolean mOnResumeNeedsLoad;
296
297 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
298 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
299
300 private Bundle mSavedInstanceState;
301
302 private LauncherModel mModel;
303 private IconCache mIconCache;
304 private boolean mUserPresent = true;
305 private boolean mVisible = false;
306 private boolean mHasFocus = false;
307 private boolean mAttached = false;
308
309 private static LocaleConfiguration sLocaleConfiguration = null;
310
311 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
312
313 private View.OnTouchListener mHapticFeedbackTouchListener;
314
315 // Related to the auto-advancing of widgets
316 private final int ADVANCE_MSG = 1;
317 private final int mAdvanceInterval = 20000;
318 private final int mAdvanceStagger = 250;
319 private long mAutoAdvanceSentTime;
320 private long mAutoAdvanceTimeLeft = -1;
321 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance =
322 new HashMap<View, AppWidgetProviderInfo>();
323
324 // Determines how long to wait after a rotation before restoring the screen orientation to
325 // match the sensor state.
326 private final int mRestoreScreenOrientationDelay = 500;
327
328 // External icons saved in case of resource changes, orientation, etc.
329 private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
330 private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
331
332 private Drawable mWorkspaceBackgroundDrawable;
333
334 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
335 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
336
337 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
338 static Date sDateStamp = new Date();
339 static DateFormat sDateFormat =
340 DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
341 static long sRunStart = System.currentTimeMillis();
342 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
343
344 // We only want to get the SharedPreferences once since it does an FS stat each time we get
345 // it from the context.
346 private SharedPreferences mSharedPrefs;
347
348 private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null;
349
350 // Holds the page that we need to animate to, and the icon views that we need to animate up
351 // when we scroll to that page on resume.
352 private ImageView mFolderIconImageView;
353 private Bitmap mFolderIconBitmap;
354 private Canvas mFolderIconCanvas;
355 private Rect mRectForFolderAnimation = new Rect();
356
357 private BubbleTextView mWaitingForResume;
358
359 private Runnable mBuildLayersRunnable = new Runnable() {
360 public void run() {
361 if (mWorkspace != null) {
362 mWorkspace.buildPageHardwareLayers();
363 }
364 }
365 };
366
367 private static PendingAddArguments sPendingAddItem;
368
369 public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
370
371 private static class PendingAddArguments {
372 int requestCode;
373 Intent intent;
374 long container;
375 long screenId;
376 int cellX;
377 int cellY;
378 int appWidgetId;
379 }
380
381 private Stats mStats;
382
383 FocusIndicatorView mFocusHandler;
384
385 static boolean isPropertyEnabled(String propertyName) {
386 return Log.isLoggable(propertyName, Log.VERBOSE);
387 }
388
389 @Override
390 protected void onCreate(Bundle savedInstanceState) {
391 if (DEBUG_STRICT_MODE) {
392 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
393 .detectDiskReads()
394 .detectDiskWrites()
395 .detectNetwork() // or .detectAll() for all detectable problems
396 .penaltyLog()
397 .build());
398 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
399 .detectLeakedSqlLiteObjects()
400 .detectLeakedClosableObjects()
401 .penaltyLog()
402 .penaltyDeath()
403 .build());
404 }
405
406 super.onCreate(savedInstanceState);
407
408 LauncherAppState.setApplicationContext(getApplicationContext());
409 LauncherAppState app = LauncherAppState.getInstance();
410 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
411 // Determine the dynamic grid properties
412 Point smallestSize = new Point();
413 Point largestSize = new Point();
414 Point realSize = new Point();
415 Display display = getWindowManager().getDefaultDisplay();
416 display.getCurrentSizeRange(smallestSize, largestSize);
417 display.getRealSize(realSize);
418 DisplayMetrics dm = new DisplayMetrics();
419 display.getMetrics(dm);
420
421 // Lazy-initialize the dynamic grid
422 DeviceProfile grid = app.initDynamicGrid(this,
423 Math.min(smallestSize.x, smallestSize.y),
424 Math.min(largestSize.x, largestSize.y),
425 realSize.x, realSize.y,
426 dm.widthPixels, dm.heightPixels);
427
428 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
429 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
430 Context.MODE_PRIVATE);
431 mIsSafeModeEnabled = getPackageManager().isSafeMode();
432 mModel = app.setLauncher(this);
433 mIconCache = app.getIconCache();
434 mIconCache.flushInvalidIcons(grid);
435 mDragController = new DragController(this);
436 mInflater = getLayoutInflater();
437
438 mStats = new Stats(this);
439
440 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
441
442 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
443 mAppWidgetHost.startListening();
444
445 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
446 // this also ensures that any synchronous binding below doesn't re-trigger another
447 // LauncherModel load.
448 mPaused = false;
449
450 if (PROFILE_STARTUP) {
451 android.os.Debug.startMethodTracing(
452 Environment.getExternalStorageDirectory() + "/launcher");
453 }
454
455 checkForLocaleChange();
456 setContentView(R.layout.launcher);
457
458 setupViews();
459 grid.layout(this);
460
461 registerContentObservers();
462
463 lockAllApps();
464
465 mSavedState = savedInstanceState;
466 restoreState(mSavedState);
467
468 if (PROFILE_STARTUP) {
469 android.os.Debug.stopMethodTracing();
470 }
471
472 if (!mRestoring) {
473 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
474 // If the user leaves launcher, then we should just load items asynchronously when
475 // they return.
476 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
477 } else {
478 // We only load the page synchronously if the user rotates (or triggers a
479 // configuration change) while launcher is in the foreground
480 mModel.startLoader(true, mWorkspace.getRestorePage());
481 }
482 }
483
484 // For handling default keys
485 mDefaultKeySsb = new SpannableStringBuilder();
486 Selection.setSelection(mDefaultKeySsb, 0);
487
488 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
489 registerReceiver(mCloseSystemDialogsReceiver, filter);
490
491 updateGlobalIcons();
492
493 // On large interfaces, we want the screen to auto-rotate based on the current orientation
494 unlockScreenOrientation(true);
495
496 if (shouldShowIntroScreen()) {
497 showIntroScreen();
498 } else {
499 showFirstRunActivity();
500 showFirstRunClings();
501 }
502 }
503
504 @Override
505 public void onLauncherProviderChange() { }
506
507 /** To be overriden by subclasses to hint to Launcher that we have custom content */
508 protected boolean hasCustomContentToLeft() {
509 return false;
510 }
511
512 /**
513 * To be overridden by subclasses to populate the custom content container and call
514 * {@link #addToCustomContentPage}. This will only be invoked if
515 * {@link #hasCustomContentToLeft()} is {@code true}.
516 */
517 protected void populateCustomContentContainer() {
518 }
519
520 /**
521 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
522 * ensure the custom content page is added or removed if necessary.
523 */
524 protected void invalidateHasCustomContentToLeft() {
525 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
526 // Not bound yet, wait for bindScreens to be called.
527 return;
528 }
529
530 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
531 // Create the custom content page and call the subclass to populate it.
532 mWorkspace.createCustomContentContainer();
533 populateCustomContentContainer();
534 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
535 mWorkspace.removeCustomContentPage();
536 }
537 }
538
539 private void updateGlobalIcons() {
540 boolean searchVisible = false;
541 boolean voiceVisible = false;
542 // If we have a saved version of these external icons, we load them up immediately
543 int coi = getCurrentOrientationIndexForGlobalIcons();
544 if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null) {
545 searchVisible = updateGlobalSearchIcon();
546 voiceVisible = updateVoiceSearchIcon(searchVisible);
547 }
548 if (sGlobalSearchIcon[coi] != null) {
549 updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
550 searchVisible = true;
551 }
552 if (sVoiceSearchIcon[coi] != null) {
553 updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
554 voiceVisible = true;
555 }
556 if (mSearchDropTargetBar != null) {
557 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
558 }
559 }
560
561 private void checkForLocaleChange() {
562 if (sLocaleConfiguration == null) {
563 new AsyncTask<Void, Void, LocaleConfiguration>() {
564 @Override
565 protected LocaleConfiguration doInBackground(Void... unused) {
566 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
567 readConfiguration(Launcher.this, localeConfiguration);
568 return localeConfiguration;
569 }
570
571 @Override
572 protected void onPostExecute(LocaleConfiguration result) {
573 sLocaleConfiguration = result;
574 checkForLocaleChange(); // recursive, but now with a locale configuration
575 }
576 }.execute();
577 return;
578 }
579
580 final Configuration configuration = getResources().getConfiguration();
581
582 final String previousLocale = sLocaleConfiguration.locale;
583 final String locale = configuration.locale.toString();
584
585 final int previousMcc = sLocaleConfiguration.mcc;
586 final int mcc = configuration.mcc;
587
588 final int previousMnc = sLocaleConfiguration.mnc;
589 final int mnc = configuration.mnc;
590
591 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
592
593 if (localeChanged) {
594 sLocaleConfiguration.locale = locale;
595 sLocaleConfiguration.mcc = mcc;
596 sLocaleConfiguration.mnc = mnc;
597
598 mIconCache.flush();
599
600 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
601 new AsyncTask<Void, Void, Void>() {
602 public Void doInBackground(Void ... args) {
603 writeConfiguration(Launcher.this, localeConfiguration);
604 return null;
605 }
606 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
607 }
608 }
609
610 private static class LocaleConfiguration {
611 public String locale;
612 public int mcc = -1;
613 public int mnc = -1;
614 }
615
616 private static void readConfiguration(Context context, LocaleConfiguration configuration) {
617 DataInputStream in = null;
618 try {
619 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFS));
620 configuration.locale = in.readUTF();
621 configuration.mcc = in.readInt();
622 configuration.mnc = in.readInt();
623 } catch (FileNotFoundException e) {
624 // Ignore
625 } catch (IOException e) {
626 // Ignore
627 } finally {
628 if (in != null) {
629 try {
630 in.close();
631 } catch (IOException e) {
632 // Ignore
633 }
634 }
635 }
636 }
637
638 private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
639 DataOutputStream out = null;
640 try {
641 out = new DataOutputStream(context.openFileOutput(
642 LauncherFiles.LAUNCHER_PREFS, MODE_PRIVATE));
643 out.writeUTF(configuration.locale);
644 out.writeInt(configuration.mcc);
645 out.writeInt(configuration.mnc);
646 out.flush();
647 } catch (FileNotFoundException e) {
648 // Ignore
649 } catch (IOException e) {
650 //noinspection ResultOfMethodCallIgnored
651 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFS).delete();
652 } finally {
653 if (out != null) {
654 try {
655 out.close();
656 } catch (IOException e) {
657 // Ignore
658 }
659 }
660 }
661 }
662
663 public Stats getStats() {
664 return mStats;
665 }
666
667 public LayoutInflater getInflater() {
668 return mInflater;
669 }
670
671 boolean isDraggingEnabled() {
672 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
673 // that is subsequently removed from the workspace in startBinding().
674 return !mModel.isLoadingWorkspace();
675 }
676
677 static int getScreen() {
678 synchronized (sLock) {
679 return sScreen;
680 }
681 }
682
683 static void setScreen(int screen) {
684 synchronized (sLock) {
685 sScreen = screen;
686 }
687 }
688
689 public static int generateViewId() {
690 if (Build.VERSION.SDK_INT >= 17) {
691 return View.generateViewId();
692 } else {
693 // View.generateViewId() is not available. The following fallback logic is a copy
694 // of its implementation.
695 for (;;) {
696 final int result = sNextGeneratedId.get();
697 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
698 int newValue = result + 1;
699 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
700 if (sNextGeneratedId.compareAndSet(result, newValue)) {
701 return result;
702 }
703 }
704 }
705 }
706
707 public int getViewIdForItem(ItemInfo info) {
708 // This cast is safe given the > 2B range for int.
709 int itemId = (int) info.id;
710 if (mItemIdToViewId.containsKey(itemId)) {
711 return mItemIdToViewId.get(itemId);
712 }
713 int viewId = generateViewId();
714 mItemIdToViewId.put(itemId, viewId);
715 return viewId;
716 }
717
718 /**
719 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
720 * a configuration step, this allows the proper animations to run after other transitions.
721 */
722 private long completeAdd(PendingAddArguments args) {
723 long screenId = args.screenId;
724 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
725 // When the screen id represents an actual screen (as opposed to a rank) we make sure
726 // that the drop page actually exists.
727 screenId = ensurePendingDropLayoutExists(args.screenId);
728 }
729
730 switch (args.requestCode) {
731 case REQUEST_CREATE_SHORTCUT:
732 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
733 args.cellY);
734 break;
735 case REQUEST_CREATE_APPWIDGET:
736 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
737 break;
738 case REQUEST_RECONFIGURE_APPWIDGET:
739 completeRestoreAppWidget(args.appWidgetId);
740 break;
741 }
742 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
743 // if you turned the screen off and then back while in All Apps, Launcher would not
744 // return to the workspace. Clearing mAddInfo.container here fixes this issue
745 resetAddInfo();
746 return screenId;
747 }
748
749 @Override
750 protected void onActivityResult(
751 final int requestCode, final int resultCode, final Intent data) {
752 // Reset the startActivity waiting flag
753 setWaitingForResult(false);
754 final int pendingAddWidgetId = mPendingAddWidgetId;
755 mPendingAddWidgetId = -1;
756
757 Runnable exitSpringLoaded = new Runnable() {
758 @Override
759 public void run() {
760 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
761 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
762 }
763 };
764
765 if (requestCode == REQUEST_BIND_APPWIDGET) {
766 final int appWidgetId = data != null ?
767 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
768 if (resultCode == RESULT_CANCELED) {
769 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
770 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
771 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
772 } else if (resultCode == RESULT_OK) {
773 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
774 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
775 }
776 return;
777 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
778 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
779 mWorkspace.exitOverviewMode(false);
780 }
781 return;
782 }
783
784 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
785 requestCode == REQUEST_CREATE_APPWIDGET);
786
787 final boolean workspaceLocked = isWorkspaceLocked();
788 // We have special handling for widgets
789 if (isWidgetDrop) {
790 final int appWidgetId;
791 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
792 : -1;
793 if (widgetId < 0) {
794 appWidgetId = pendingAddWidgetId;
795 } else {
796 appWidgetId = widgetId;
797 }
798
799 final int result;
800 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
801 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
802 "returned from the widget configuration activity.");
803 result = RESULT_CANCELED;
804 completeTwoStageWidgetDrop(result, appWidgetId);
805 final Runnable onComplete = new Runnable() {
806 @Override
807 public void run() {
808 exitSpringLoadedDragModeDelayed(false, 0, null);
809 }
810 };
811 if (workspaceLocked) {
812 // No need to remove the empty screen if we're mid-binding, as the
813 // the bind will not add the empty screen.
814 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
815 } else {
816 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
817 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
818 }
819 } else {
820 if (!workspaceLocked) {
821 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
822 // When the screen id represents an actual screen (as opposed to a rank)
823 // we make sure that the drop page actually exists.
824 mPendingAddInfo.screenId =
825 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
826 }
827 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
828
829 dropLayout.setDropPending(true);
830 final Runnable onComplete = new Runnable() {
831 @Override
832 public void run() {
833 completeTwoStageWidgetDrop(resultCode, appWidgetId);
834 dropLayout.setDropPending(false);
835 }
836 };
837 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
838 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
839 } else {
840 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
841 mPendingAddInfo);
842 sPendingAddItem = args;
843 }
844 }
845 return;
846 }
847
848 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
849 if (resultCode == RESULT_OK) {
850 // Update the widget view.
851 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
852 pendingAddWidgetId, mPendingAddInfo);
853 if (workspaceLocked) {
854 sPendingAddItem = args;
855 } else {
856 completeAdd(args);
857 }
858 }
859 // Leave the widget in the pending state if the user canceled the configure.
860 return;
861 }
862
863 // The pattern used here is that a user PICKs a specific application,
864 // which, depending on the target, might need to CREATE the actual target.
865
866 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
867 // launch over to the Music app to actually CREATE_SHORTCUT.
868 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
869 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
870 mPendingAddInfo);
871 if (isWorkspaceLocked()) {
872 sPendingAddItem = args;
873 } else {
874 completeAdd(args);
875 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
876 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
877 }
878 } else if (resultCode == RESULT_CANCELED) {
879 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
880 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
881 }
882 mDragLayer.clearAnimatedView();
883 }
884
885 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
886 appWidgetId, ItemInfo info) {
887 PendingAddArguments args = new PendingAddArguments();
888 args.requestCode = requestCode;
889 args.intent = data;
890 args.container = info.container;
891 args.screenId = info.screenId;
892 args.cellX = info.cellX;
893 args.cellY = info.cellY;
894 args.appWidgetId = appWidgetId;
895 return args;
896 }
897
898 /**
899 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
900 *
901 * @param screenId the screen id to check
902 * @return the new screen, or screenId if it exists
903 */
904 private long ensurePendingDropLayoutExists(long screenId) {
905 CellLayout dropLayout =
906 (CellLayout) mWorkspace.getScreenWithId(screenId);
907 if (dropLayout == null) {
908 // it's possible that the add screen was removed because it was
909 // empty and a re-bind occurred
910 mWorkspace.addExtraEmptyScreen();
911 return mWorkspace.commitExtraEmptyScreen();
912 } else {
913 return screenId;
914 }
915 }
916
917 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
918 CellLayout cellLayout =
919 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
920 Runnable onCompleteRunnable = null;
921 int animationType = 0;
922
923 AppWidgetHostView boundWidget = null;
924 if (resultCode == RESULT_OK) {
925 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
926 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
927 mPendingAddWidgetInfo);
928 boundWidget = layout;
929 onCompleteRunnable = new Runnable() {
930 @Override
931 public void run() {
932 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
933 mPendingAddInfo.screenId, layout, null);
934 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
935 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
936 }
937 };
938 } else if (resultCode == RESULT_CANCELED) {
939 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
940 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
941 }
942 if (mDragLayer.getAnimatedView() != null) {
943 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
944 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
945 animationType, boundWidget, true);
946 } else if (onCompleteRunnable != null) {
947 // The animated view may be null in the case of a rotation during widget configuration
948 onCompleteRunnable.run();
949 }
950 }
951
952 @Override
953 protected void onStop() {
954 super.onStop();
955 FirstFrameAnimatorHelper.setIsVisible(false);
956 }
957
958 @Override
959 protected void onStart() {
960 super.onStart();
961 FirstFrameAnimatorHelper.setIsVisible(true);
962 }
963
964 @Override
965 protected void onResume() {
966 long startTime = 0;
967 if (DEBUG_RESUME_TIME) {
968 startTime = System.currentTimeMillis();
969 Log.v(TAG, "Launcher.onResume()");
970 }
971 super.onResume();
972
973 // Restore the previous launcher state
974 if (mOnResumeState == State.WORKSPACE) {
975 showWorkspace(false);
976 } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
977 showAllApps(false, mAppsCustomizeContent.getContentType(), false);
978 }
979 mOnResumeState = State.NONE;
980
981 // Background was set to gradient in onPause(), restore to black if in all apps.
982 setWorkspaceBackground(mState == State.WORKSPACE);
983
984 mPaused = false;
985 if (mRestoring || mOnResumeNeedsLoad) {
986 setWorkspaceLoading(true);
987 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
988 mRestoring = false;
989 mOnResumeNeedsLoad = false;
990 }
991 if (mBindOnResumeCallbacks.size() > 0) {
992 // We might have postponed some bind calls until onResume (see waitUntilResume) --
993 // execute them here
994 long startTimeCallbacks = 0;
995 if (DEBUG_RESUME_TIME) {
996 startTimeCallbacks = System.currentTimeMillis();
997 }
998
999 if (mAppsCustomizeContent != null) {
1000 mAppsCustomizeContent.setBulkBind(true);
1001 }
1002 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1003 mBindOnResumeCallbacks.get(i).run();
1004 }
1005 if (mAppsCustomizeContent != null) {
1006 mAppsCustomizeContent.setBulkBind(false);
1007 }
1008 mBindOnResumeCallbacks.clear();
1009 if (DEBUG_RESUME_TIME) {
1010 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1011 (System.currentTimeMillis() - startTimeCallbacks));
1012 }
1013 }
1014 if (mOnResumeCallbacks.size() > 0) {
1015 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1016 mOnResumeCallbacks.get(i).run();
1017 }
1018 mOnResumeCallbacks.clear();
1019 }
1020
1021 // Reset the pressed state of icons that were locked in the press state while activities
1022 // were launching
1023 if (mWaitingForResume != null) {
1024 // Resets the previous workspace icon press state
1025 mWaitingForResume.setStayPressed(false);
1026 }
1027
1028 // It is possible that widgets can receive updates while launcher is not in the foreground.
1029 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1030 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1031 // orientation.
1032 getWorkspace().reinflateWidgetsIfNecessary();
1033
1034 // Process any items that were added while Launcher was away.
1035 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1036
1037 // Update the voice search button proxy
1038 updateVoiceButtonProxyVisible(false);
1039
1040 // Again, as with the above scenario, it's possible that one or more of the global icons
1041 // were updated in the wrong orientation.
1042 updateGlobalIcons();
1043 if (DEBUG_RESUME_TIME) {
1044 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1045 }
1046
1047 if (mWorkspace.getCustomContentCallbacks() != null) {
1048 // If we are resuming and the custom content is the current page, we call onShow().
1049 // It is also poassible that onShow will instead be called slightly after first layout
1050 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1051 if (mWorkspace.isOnOrMovingToCustomContent()) {
1052 mWorkspace.getCustomContentCallbacks().onShow(true);
1053 }
1054 }
1055 mWorkspace.updateInteractionForState();
1056 mWorkspace.onResume();
1057
1058 PackageInstallerCompat.getInstance(this).onResume();
1059 }
1060
1061 @Override
1062 protected void onPause() {
1063 // Ensure that items added to Launcher are queued until Launcher returns
1064 InstallShortcutReceiver.enableInstallQueue();
1065 PackageInstallerCompat.getInstance(this).onPause();
1066
1067 super.onPause();
1068 mPaused = true;
1069 mDragController.cancelDrag();
1070 mDragController.resetLastGestureUpTime();
1071
1072 // We call onHide() aggressively. The custom content callbacks should be able to
1073 // debounce excess onHide calls.
1074 if (mWorkspace.getCustomContentCallbacks() != null) {
1075 mWorkspace.getCustomContentCallbacks().onHide();
1076 }
1077 }
1078
1079 QSBScroller mQsbScroller = new QSBScroller() {
1080 int scrollY = 0;
1081
1082 @Override
1083 public void setScrollY(int scroll) {
1084 scrollY = scroll;
1085
1086 if (mWorkspace.isOnOrMovingToCustomContent()) {
1087 mSearchDropTargetBar.setTranslationY(- scrollY);
1088 getQsbBar().setTranslationY(-scrollY);
1089 }
1090 }
1091 };
1092
1093 public void resetQSBScroll() {
1094 mSearchDropTargetBar.animate().translationY(0).start();
1095 getQsbBar().animate().translationY(0).start();
1096 }
1097
1098 public interface CustomContentCallbacks {
1099 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1100 // by a onResume or by scrolling otherwise.
1101 public void onShow(boolean fromResume);
1102
1103 // Custom content is completely hidden
1104 public void onHide();
1105
1106 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1107 public void onScrollProgressChanged(float progress);
1108
1109 // Indicates whether the user is allowed to scroll away from the custom content.
1110 boolean isScrollingAllowed();
1111 }
1112
1113 protected boolean hasSettings() {
1114 return false;
1115 }
1116
1117 public interface QSBScroller {
1118 public void setScrollY(int scrollY);
1119 }
1120
1121 public QSBScroller addToCustomContentPage(View customContent,
1122 CustomContentCallbacks callbacks, String description) {
1123 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1124 return mQsbScroller;
1125 }
1126
1127 // The custom content needs to offset its content to account for the QSB
1128 public int getTopOffsetForCustomContent() {
1129 return mWorkspace.getPaddingTop();
1130 }
1131
1132 @Override
1133 public Object onRetainNonConfigurationInstance() {
1134 // Flag the loader to stop early before switching
1135 if (mModel.isCurrentCallbacks(this)) {
1136 mModel.stopLoader();
1137 }
1138 if (mAppsCustomizeContent != null) {
1139 mAppsCustomizeContent.surrender();
1140 }
1141 return Boolean.TRUE;
1142 }
1143
1144 // We can't hide the IME if it was forced open. So don't bother
1145 @Override
1146 public void onWindowFocusChanged(boolean hasFocus) {
1147 super.onWindowFocusChanged(hasFocus);
1148 mHasFocus = hasFocus;
1149 }
1150
1151 private boolean acceptFilter() {
1152 final InputMethodManager inputManager = (InputMethodManager)
1153 getSystemService(Context.INPUT_METHOD_SERVICE);
1154 return !inputManager.isFullscreenMode();
1155 }
1156
1157 @Override
1158 public boolean onKeyDown(int keyCode, KeyEvent event) {
1159 final int uniChar = event.getUnicodeChar();
1160 final boolean handled = super.onKeyDown(keyCode, event);
1161 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1162 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1163 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1164 keyCode, event);
1165 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1166 // something usable has been typed - start a search
1167 // the typed text will be retrieved and cleared by
1168 // showSearchDialog()
1169 // If there are multiple keystrokes before the search dialog takes focus,
1170 // onSearchRequested() will be called for every keystroke,
1171 // but it is idempotent, so it's fine.
1172 return onSearchRequested();
1173 }
1174 }
1175
1176 // Eat the long press event so the keyboard doesn't come up.
1177 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1178 return true;
1179 }
1180
1181 return handled;
1182 }
1183
1184 private String getTypedText() {
1185 return mDefaultKeySsb.toString();
1186 }
1187
1188 private void clearTypedText() {
1189 mDefaultKeySsb.clear();
1190 mDefaultKeySsb.clearSpans();
1191 Selection.setSelection(mDefaultKeySsb, 0);
1192 }
1193
1194 /**
1195 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1196 * State
1197 */
1198 private static State intToState(int stateOrdinal) {
1199 State state = State.WORKSPACE;
1200 final State[] stateValues = State.values();
1201 for (int i = 0; i < stateValues.length; i++) {
1202 if (stateValues[i].ordinal() == stateOrdinal) {
1203 state = stateValues[i];
1204 break;
1205 }
1206 }
1207 return state;
1208 }
1209
1210 /**
1211 * Restores the previous state, if it exists.
1212 *
1213 * @param savedState The previous state.
1214 */
1215 @SuppressWarnings("unchecked")
1216 private void restoreState(Bundle savedState) {
1217 if (savedState == null) {
1218 return;
1219 }
1220
1221 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1222 if (state == State.APPS_CUSTOMIZE) {
1223 mOnResumeState = State.APPS_CUSTOMIZE;
1224 }
1225
1226 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1227 PagedView.INVALID_RESTORE_PAGE);
1228 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1229 mWorkspace.setRestorePage(currentScreen);
1230 }
1231
1232 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1233 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1234
1235 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1236 mPendingAddInfo.container = pendingAddContainer;
1237 mPendingAddInfo.screenId = pendingAddScreen;
1238 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1239 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1240 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1241 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1242 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1243 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1244 setWaitingForResult(true);
1245 mRestoring = true;
1246 }
1247
1248 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1249 if (renameFolder) {
1250 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1251 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1252 mRestoring = true;
1253 }
1254
1255 // Restore the AppsCustomize tab
1256 if (mAppsCustomizeTabHost != null) {
1257 String curTab = savedState.getString("apps_customize_currentTab");
1258 if (curTab != null) {
1259 mAppsCustomizeTabHost.setContentTypeImmediate(
1260 mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
1261 mAppsCustomizeContent.loadAssociatedPages(
1262 mAppsCustomizeContent.getCurrentPage());
1263 }
1264
1265 int currentIndex = savedState.getInt("apps_customize_currentIndex");
1266 mAppsCustomizeContent.restorePageForIndex(currentIndex);
1267 }
1268 mItemIdToViewId = (HashMap<Integer, Integer>)
1269 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1270 }
1271
1272 /**
1273 * Finds all the views we need and configure them properly.
1274 */
1275 private void setupViews() {
1276 final DragController dragController = mDragController;
1277
1278 mLauncherView = findViewById(R.id.launcher);
1279 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1280 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1281 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1282 mWorkspace.setPageSwitchListener(this);
1283 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1284
1285 mLauncherView.setSystemUiVisibility(
1286 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1287 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1288
1289 // Setup the drag layer
1290 mDragLayer.setup(this, dragController);
1291
1292 // Setup the hotseat
1293 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1294 if (mHotseat != null) {
1295 mHotseat.setup(this);
1296 mHotseat.setOnLongClickListener(this);
1297 }
1298
1299 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1300 View widgetButton = findViewById(R.id.widget_button);
1301 widgetButton.setOnClickListener(new OnClickListener() {
1302 @Override
1303 public void onClick(View arg0) {
1304 if (!mWorkspace.isSwitchingState()) {
1305 onClickAddWidgetButton(arg0);
1306 }
1307 }
1308 });
1309 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1310
1311 View wallpaperButton = findViewById(R.id.wallpaper_button);
1312 wallpaperButton.setOnClickListener(new OnClickListener() {
1313 @Override
1314 public void onClick(View arg0) {
1315 if (!mWorkspace.isSwitchingState()) {
1316 onClickWallpaperPicker(arg0);
1317 }
1318 }
1319 });
1320 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1321
1322 View settingsButton = findViewById(R.id.settings_button);
1323 if (hasSettings()) {
1324 settingsButton.setOnClickListener(new OnClickListener() {
1325 @Override
1326 public void onClick(View arg0) {
1327 if (!mWorkspace.isSwitchingState()) {
1328 onClickSettingsButton(arg0);
1329 }
1330 }
1331 });
1332 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1333 } else {
1334 settingsButton.setVisibility(View.GONE);
1335 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams();
1336 lp.gravity = Gravity.END | Gravity.TOP;
1337 widgetButton.requestLayout();
1338 }
1339
1340 mOverviewPanel.setAlpha(0f);
1341
1342 // Setup the workspace
1343 mWorkspace.setHapticFeedbackEnabled(false);
1344 mWorkspace.setOnLongClickListener(this);
1345 mWorkspace.setup(dragController);
1346 dragController.addDragListener(mWorkspace);
1347
1348 // Get the search/delete bar
1349 mSearchDropTargetBar = (SearchDropTargetBar)
1350 mDragLayer.findViewById(R.id.search_drop_target_bar);
1351
1352 // Setup AppsCustomize
1353 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
1354 mAppsCustomizeContent = (AppsCustomizePagedView)
1355 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
1356 mAppsCustomizeContent.setup(this, dragController);
1357
1358 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1359 dragController.setDragScoller(mWorkspace);
1360 dragController.setScrollView(mDragLayer);
1361 dragController.setMoveTarget(mWorkspace);
1362 dragController.addDropTarget(mWorkspace);
1363 if (mSearchDropTargetBar != null) {
1364 mSearchDropTargetBar.setup(this, dragController);
1365 }
1366
1367 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1368 Log.v(TAG, "adding WeightWatcher");
1369 mWeightWatcher = new WeightWatcher(this);
1370 mWeightWatcher.setAlpha(0.5f);
1371 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1372 new FrameLayout.LayoutParams(
1373 FrameLayout.LayoutParams.MATCH_PARENT,
1374 FrameLayout.LayoutParams.WRAP_CONTENT,
1375 Gravity.BOTTOM)
1376 );
1377
1378 boolean show = shouldShowWeightWatcher();
1379 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1380 }
1381 }
1382
1383 /**
1384 * Sets the all apps button. This method is called from {@link Hotseat}.
1385 */
1386 public void setAllAppsButton(View allAppsButton) {
1387 mAllAppsButton = allAppsButton;
1388 }
1389
1390 public View getAllAppsButton() {
1391 return mAllAppsButton;
1392 }
1393
1394 /**
1395 * Creates a view representing a shortcut.
1396 *
1397 * @param info The data structure describing the shortcut.
1398 *
1399 * @return A View inflated from R.layout.application.
1400 */
1401 View createShortcut(ShortcutInfo info) {
1402 return createShortcut(R.layout.application,
1403 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1404 }
1405
1406 /**
1407 * Creates a view representing a shortcut inflated from the specified resource.
1408 *
1409 * @param layoutResId The id of the XML layout used to create the shortcut.
1410 * @param parent The group the shortcut belongs to.
1411 * @param info The data structure describing the shortcut.
1412 *
1413 * @return A View inflated from layoutResId.
1414 */
1415 View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1416 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1417 favorite.applyFromShortcutInfo(info, mIconCache, true);
1418 favorite.setOnClickListener(this);
1419 favorite.setOnFocusChangeListener(mFocusHandler);
1420 return favorite;
1421 }
1422
1423 /**
1424 * Add a shortcut to the workspace.
1425 *
1426 * @param data The intent describing the shortcut.
1427 * @param cellInfo The position on screen where to create the shortcut.
1428 */
1429 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1430 int cellY) {
1431 int[] cellXY = mTmpAddItemCellCoordinates;
1432 int[] touchXY = mPendingAddInfo.dropPos;
1433 CellLayout layout = getCellLayout(container, screenId);
1434
1435 boolean foundCellSpan = false;
1436
1437 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
1438 if (info == null) {
1439 return;
1440 }
1441 final View view = createShortcut(info);
1442
1443 // First we check if we already know the exact location where we want to add this item.
1444 if (cellX >= 0 && cellY >= 0) {
1445 cellXY[0] = cellX;
1446 cellXY[1] = cellY;
1447 foundCellSpan = true;
1448
1449 // If appropriate, either create a folder or add to an existing folder
1450 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1451 true, null,null)) {
1452 return;
1453 }
1454 DragObject dragObject = new DragObject();
1455 dragObject.dragInfo = info;
1456 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1457 true)) {
1458 return;
1459 }
1460 } else if (touchXY != null) {
1461 // when dragging and dropping, just find the closest free spot
1462 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1463 foundCellSpan = (result != null);
1464 } else {
1465 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1466 }
1467
1468 if (!foundCellSpan) {
1469 showOutOfSpaceMessage(isHotseatLayout(layout));
1470 return;
1471 }
1472
1473 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
1474
1475 if (!mRestoring) {
1476 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1477 isWorkspaceLocked());
1478 }
1479 }
1480
1481 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1482 int minHeight) {
1483 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1484 // We want to account for the extra amount of padding that we are adding to the widget
1485 // to ensure that it gets the full amount of space that it has requested
1486 int requiredWidth = minWidth + padding.left + padding.right;
1487 int requiredHeight = minHeight + padding.top + padding.bottom;
1488 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1489 }
1490
1491 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1492 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1493 }
1494
1495 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1496 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1497 }
1498
1499 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1500 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1501 }
1502
1503 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1504 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1505 info.minResizeHeight);
1506 }
1507
1508 /**
1509 * Add a widget to the workspace.
1510 *
1511 * @param appWidgetId The app widget id
1512 * @param cellInfo The position on screen where to create the widget.
1513 */
1514 private void completeAddAppWidget(final int appWidgetId, long container, long screenId,
1515 AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
1516 if (appWidgetInfo == null) {
1517 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1518 }
1519
1520 // Calculate the grid spans needed to fit this widget
1521 CellLayout layout = getCellLayout(container, screenId);
1522
1523 int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
1524 int[] spanXY = getSpanForWidget(this, appWidgetInfo);
1525
1526 // Try finding open space on Launcher screen
1527 // We have saved the position to which the widget was dragged-- this really only matters
1528 // if we are placing widgets on a "spring-loaded" screen
1529 int[] cellXY = mTmpAddItemCellCoordinates;
1530 int[] touchXY = mPendingAddInfo.dropPos;
1531 int[] finalSpan = new int[2];
1532 boolean foundCellSpan = false;
1533 if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
1534 cellXY[0] = mPendingAddInfo.cellX;
1535 cellXY[1] = mPendingAddInfo.cellY;
1536 spanXY[0] = mPendingAddInfo.spanX;
1537 spanXY[1] = mPendingAddInfo.spanY;
1538 foundCellSpan = true;
1539 } else if (touchXY != null) {
1540 // when dragging and dropping, just find the closest free spot
1541 int[] result = layout.findNearestVacantArea(
1542 touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
1543 spanXY[1], cellXY, finalSpan);
1544 spanXY[0] = finalSpan[0];
1545 spanXY[1] = finalSpan[1];
1546 foundCellSpan = (result != null);
1547 } else {
1548 foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
1549 }
1550
1551 if (!foundCellSpan) {
1552 if (appWidgetId != -1) {
1553 // Deleting an app widget ID is a void call but writes to disk before returning
1554 // to the caller...
1555 new AsyncTask<Void, Void, Void>() {
1556 public Void doInBackground(Void ... args) {
1557 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1558 return null;
1559 }
1560 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1561 }
1562 showOutOfSpaceMessage(isHotseatLayout(layout));
1563 return;
1564 }
1565
1566 // Build Launcher-specific widget info and save to database
1567 LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
1568 appWidgetInfo.provider);
1569 launcherInfo.spanX = spanXY[0];
1570 launcherInfo.spanY = spanXY[1];
1571 launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
1572 launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
1573 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1574
1575 LauncherModel.addItemToDatabase(this, launcherInfo,
1576 container, screenId, cellXY[0], cellXY[1], false);
1577
1578 if (!mRestoring) {
1579 if (hostView == null) {
1580 // Perform actual inflation because we're live
1581 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1582 launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
1583 } else {
1584 // The AppWidgetHostView has already been inflated and instantiated
1585 launcherInfo.hostView = hostView;
1586 }
1587
1588 launcherInfo.hostView.setTag(launcherInfo);
1589 launcherInfo.hostView.setVisibility(View.VISIBLE);
1590 launcherInfo.notifyWidgetSizeChanged(this);
1591
1592 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, cellXY[0], cellXY[1],
1593 launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1594
1595 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1596 }
1597 resetAddInfo();
1598 }
1599
1600 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1601 @Override
1602 public void onReceive(Context context, Intent intent) {
1603 final String action = intent.getAction();
1604 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1605 mUserPresent = false;
1606 mDragLayer.clearAllResizeFrames();
1607 updateRunning();
1608
1609 // Reset AllApps to its initial state only if we are not in the middle of
1610 // processing a multi-step drop
1611 if (mAppsCustomizeTabHost != null && mPendingAddInfo.container == ItemInfo.NO_ID) {
1612 showWorkspace(false);
1613 }
1614 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1615 mUserPresent = true;
1616 updateRunning();
1617 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1618 mModel.resetLoadedState(false, true);
1619 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1620 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
1621 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1622 mModel.resetLoadedState(false, true);
1623 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
1624 LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
1625 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
1626 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1627 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1628 getModel().forceReload();
1629 }
1630 }
1631 };
1632
1633 @Override
1634 public void onAttachedToWindow() {
1635 super.onAttachedToWindow();
1636
1637 // Listen for broadcasts related to user-presence
1638 final IntentFilter filter = new IntentFilter();
1639 filter.addAction(Intent.ACTION_SCREEN_OFF);
1640 filter.addAction(Intent.ACTION_USER_PRESENT);
1641 // For handling managed profiles
1642 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1643 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1644 if (ENABLE_DEBUG_INTENTS) {
1645 filter.addAction(DebugIntents.DELETE_DATABASE);
1646 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1647 }
1648 registerReceiver(mReceiver, filter);
1649 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1650 setupTransparentSystemBarsForLmp();
1651 mAttached = true;
1652 mVisible = true;
1653 }
1654
1655 /**
1656 * Sets up transparent navigation and status bars in LMP.
1657 * This method is a no-op for other platform versions.
1658 */
1659 @TargetApi(19)
1660 private void setupTransparentSystemBarsForLmp() {
1661 // TODO(sansid): use the APIs directly when compiling against L sdk.
1662 // Currently we use reflection to access the flags and the API to set the transparency
1663 // on the System bars.
1664 if (Utilities.isLmpOrAbove()) {
1665 try {
1666 getWindow().getAttributes().systemUiVisibility |=
1667 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1668 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1669 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1670 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1671 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1672 Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField(
1673 "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS");
1674 getWindow().addFlags(drawsSysBackgroundsField.getInt(null));
1675
1676 Method setStatusBarColorMethod =
1677 Window.class.getDeclaredMethod("setStatusBarColor", int.class);
1678 Method setNavigationBarColorMethod =
1679 Window.class.getDeclaredMethod("setNavigationBarColor", int.class);
1680 setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
1681 setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
1682 } catch (NoSuchFieldException e) {
1683 Log.w(TAG, "NoSuchFieldException while setting up transparent bars");
1684 } catch (NoSuchMethodException ex) {
1685 Log.w(TAG, "NoSuchMethodException while setting up transparent bars");
1686 } catch (IllegalAccessException e) {
1687 Log.w(TAG, "IllegalAccessException while setting up transparent bars");
1688 } catch (IllegalArgumentException e) {
1689 Log.w(TAG, "IllegalArgumentException while setting up transparent bars");
1690 } catch (InvocationTargetException e) {
1691 Log.w(TAG, "InvocationTargetException while setting up transparent bars");
1692 } finally {}
1693 }
1694 }
1695
1696 @Override
1697 public void onDetachedFromWindow() {
1698 super.onDetachedFromWindow();
1699 mVisible = false;
1700
1701 if (mAttached) {
1702 unregisterReceiver(mReceiver);
1703 mAttached = false;
1704 }
1705 updateRunning();
1706 }
1707
1708 public void onWindowVisibilityChanged(int visibility) {
1709 mVisible = visibility == View.VISIBLE;
1710 updateRunning();
1711 // The following code used to be in onResume, but it turns out onResume is called when
1712 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1713 // is a more appropriate event to handle
1714 if (mVisible) {
1715 mAppsCustomizeTabHost.onWindowVisible();
1716 if (!mWorkspaceLoading) {
1717 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1718 // We want to let Launcher draw itself at least once before we force it to build
1719 // layers on all the workspace pages, so that transitioning to Launcher from other
1720 // apps is nice and speedy.
1721 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1722 private boolean mStarted = false;
1723 public void onDraw() {
1724 if (mStarted) return;
1725 mStarted = true;
1726 // We delay the layer building a bit in order to give
1727 // other message processing a time to run. In particular
1728 // this avoids a delay in hiding the IME if it was
1729 // currently shown, because doing that may involve
1730 // some communication back with the app.
1731 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1732 final ViewTreeObserver.OnDrawListener listener = this;
1733 mWorkspace.post(new Runnable() {
1734 public void run() {
1735 if (mWorkspace != null &&
1736 mWorkspace.getViewTreeObserver() != null) {
1737 mWorkspace.getViewTreeObserver().
1738 removeOnDrawListener(listener);
1739 }
1740 }
1741 });
1742 return;
1743 }
1744 });
1745 }
1746 clearTypedText();
1747 }
1748 }
1749
1750 private void sendAdvanceMessage(long delay) {
1751 mHandler.removeMessages(ADVANCE_MSG);
1752 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1753 mHandler.sendMessageDelayed(msg, delay);
1754 mAutoAdvanceSentTime = System.currentTimeMillis();
1755 }
1756
1757 private void updateRunning() {
1758 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1759 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1760 mAutoAdvanceRunning = autoAdvanceRunning;
1761 if (autoAdvanceRunning) {
1762 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1763 sendAdvanceMessage(delay);
1764 } else {
1765 if (!mWidgetsToAdvance.isEmpty()) {
1766 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1767 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1768 }
1769 mHandler.removeMessages(ADVANCE_MSG);
1770 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1771 }
1772 }
1773 }
1774
1775 private final Handler mHandler = new Handler() {
1776 @Override
1777 public void handleMessage(Message msg) {
1778 if (msg.what == ADVANCE_MSG) {
1779 int i = 0;
1780 for (View key: mWidgetsToAdvance.keySet()) {
1781 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1782 final int delay = mAdvanceStagger * i;
1783 if (v instanceof Advanceable) {
1784 postDelayed(new Runnable() {
1785 public void run() {
1786 ((Advanceable) v).advance();
1787 }
1788 }, delay);
1789 }
1790 i++;
1791 }
1792 sendAdvanceMessage(mAdvanceInterval);
1793 }
1794 }
1795 };
1796
1797 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1798 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1799 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1800 if (v instanceof Advanceable) {
1801 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1802 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1803 updateRunning();
1804 }
1805 }
1806
1807 void removeWidgetToAutoAdvance(View hostView) {
1808 if (mWidgetsToAdvance.containsKey(hostView)) {
1809 mWidgetsToAdvance.remove(hostView);
1810 updateRunning();
1811 }
1812 }
1813
1814 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1815 removeWidgetToAutoAdvance(launcherInfo.hostView);
1816 launcherInfo.hostView = null;
1817 }
1818
1819 void showOutOfSpaceMessage(boolean isHotseatLayout) {
1820 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1821 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1822 }
1823
1824 public DragLayer getDragLayer() {
1825 return mDragLayer;
1826 }
1827
1828 public Workspace getWorkspace() {
1829 return mWorkspace;
1830 }
1831
1832 public Hotseat getHotseat() {
1833 return mHotseat;
1834 }
1835
1836 public ViewGroup getOverviewPanel() {
1837 return mOverviewPanel;
1838 }
1839
1840 public SearchDropTargetBar getSearchBar() {
1841 return mSearchDropTargetBar;
1842 }
1843
1844 public LauncherAppWidgetHost getAppWidgetHost() {
1845 return mAppWidgetHost;
1846 }
1847
1848 public LauncherModel getModel() {
1849 return mModel;
1850 }
1851
1852 protected SharedPreferences getSharedPrefs() {
1853 return mSharedPrefs;
1854 }
1855
1856 public void closeSystemDialogs() {
1857 getWindow().closeAllPanels();
1858
1859 // Whatever we were doing is hereby canceled.
1860 setWaitingForResult(false);
1861 }
1862
1863 @Override
1864 protected void onNewIntent(Intent intent) {
1865 long startTime = 0;
1866 if (DEBUG_RESUME_TIME) {
1867 startTime = System.currentTimeMillis();
1868 }
1869 super.onNewIntent(intent);
1870
1871 // Close the menu
1872 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1873 // also will cancel mWaitingForResult.
1874 closeSystemDialogs();
1875
1876 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1877 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1878 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1879
1880 if (mWorkspace == null) {
1881 // Can be cases where mWorkspace is null, this prevents a NPE
1882 return;
1883 }
1884 Folder openFolder = mWorkspace.getOpenFolder();
1885 // In all these cases, only animate if we're already on home
1886 mWorkspace.exitWidgetResizeMode();
1887 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1888 openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) {
1889 mWorkspace.moveToDefaultScreen(true);
1890 }
1891
1892 closeFolder();
1893 exitSpringLoadedDragMode();
1894
1895 // If we are already on home, then just animate back to the workspace,
1896 // otherwise, just wait until onResume to set the state back to Workspace
1897 if (alreadyOnHome) {
1898 showWorkspace(true);
1899 } else {
1900 mOnResumeState = State.WORKSPACE;
1901 }
1902
1903 final View v = getWindow().peekDecorView();
1904 if (v != null && v.getWindowToken() != null) {
1905 InputMethodManager imm = (InputMethodManager)getSystemService(
1906 INPUT_METHOD_SERVICE);
1907 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
1908 }
1909
1910 // Reset the apps customize page
1911 if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
1912 mAppsCustomizeTabHost.reset();
1913 }
1914
1915 onHomeIntent();
1916 }
1917
1918 if (DEBUG_RESUME_TIME) {
1919 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
1920 }
1921 }
1922
1923 /**
1924 * Override point for subclasses to prevent movement to the default screen when the home
1925 * button is pressed. Used (for example) in GEL, to prevent movement during a search.
1926 */
1927 protected boolean shouldMoveToDefaultScreenOnHomeIntent() {
1928 return true;
1929 }
1930
1931 /**
1932 * Override point for subclasses to provide custom behaviour for when a home intent is fired.
1933 */
1934 protected void onHomeIntent() {
1935 // Do nothing
1936 }
1937
1938 @Override
1939 public void onRestoreInstanceState(Bundle state) {
1940 super.onRestoreInstanceState(state);
1941 for (int page: mSynchronouslyBoundPages) {
1942 mWorkspace.restoreInstanceStateForChild(page);
1943 }
1944 }
1945
1946 @Override
1947 protected void onSaveInstanceState(Bundle outState) {
1948 if (mWorkspace.getChildCount() > 0) {
1949 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
1950 mWorkspace.getCurrentPageOffsetFromCustomContent());
1951 }
1952 super.onSaveInstanceState(outState);
1953
1954 outState.putInt(RUNTIME_STATE, mState.ordinal());
1955 // We close any open folder since it will not be re-opened, and we need to make sure
1956 // this state is reflected.
1957 closeFolder();
1958
1959 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
1960 mWaitingForResult) {
1961 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
1962 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
1963 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
1964 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
1965 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
1966 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
1967 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
1968 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
1969 }
1970
1971 if (mFolderInfo != null && mWaitingForResult) {
1972 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
1973 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
1974 }
1975
1976 // Save the current AppsCustomize tab
1977 if (mAppsCustomizeTabHost != null) {
1978 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
1979 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
1980 if (currentTabTag != null) {
1981 outState.putString("apps_customize_currentTab", currentTabTag);
1982 }
1983 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
1984 outState.putInt("apps_customize_currentIndex", currentIndex);
1985 }
1986 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
1987 }
1988
1989 @Override
1990 public void onDestroy() {
1991 super.onDestroy();
1992
1993 // Remove all pending runnables
1994 mHandler.removeMessages(ADVANCE_MSG);
1995 mHandler.removeMessages(0);
1996 mWorkspace.removeCallbacks(mBuildLayersRunnable);
1997
1998 // Stop callbacks from LauncherModel
1999 LauncherAppState app = (LauncherAppState.getInstance());
2000
2001 // It's possible to receive onDestroy after a new Launcher activity has
2002 // been created. In this case, don't interfere with the new Launcher.
2003 if (mModel.isCurrentCallbacks(this)) {
2004 mModel.stopLoader();
2005 app.setLauncher(null);
2006 }
2007
2008 try {
2009 mAppWidgetHost.stopListening();
2010 } catch (NullPointerException ex) {
2011 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2012 }
2013 mAppWidgetHost = null;
2014
2015 mWidgetsToAdvance.clear();
2016
2017 TextKeyListener.getInstance().release();
2018
2019 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2020 // to prevent leaking Launcher activities on orientation change.
2021 if (mModel != null) {
2022 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2023 }
2024
2025 getContentResolver().unregisterContentObserver(mWidgetObserver);
2026 unregisterReceiver(mCloseSystemDialogsReceiver);
2027
2028 mDragLayer.clearAllResizeFrames();
2029 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2030 mWorkspace.removeAllWorkspaceScreens();
2031 mWorkspace = null;
2032 mDragController = null;
2033
2034 PackageInstallerCompat.getInstance(this).onStop();
2035 LauncherAnimUtils.onDestroyActivity();
2036 }
2037
2038 public DragController getDragController() {
2039 return mDragController;
2040 }
2041
2042 @Override
2043 public void startActivityForResult(Intent intent, int requestCode) {
2044 if (requestCode >= 0) {
2045 setWaitingForResult(true);
2046 }
2047 super.startActivityForResult(intent, requestCode);
2048 }
2049
2050 /**
2051 * Indicates that we want global search for this activity by setting the globalSearch
2052 * argument for {@link #startSearch} to true.
2053 */
2054 @Override
2055 public void startSearch(String initialQuery, boolean selectInitialQuery,
2056 Bundle appSearchData, boolean globalSearch) {
2057
2058 showWorkspace(true);
2059
2060 if (initialQuery == null) {
2061 // Use any text typed in the launcher as the initial query
2062 initialQuery = getTypedText();
2063 }
2064 if (appSearchData == null) {
2065 appSearchData = new Bundle();
2066 appSearchData.putString("source", "launcher-search");
2067 }
2068 Rect sourceBounds = new Rect();
2069 if (mSearchDropTargetBar != null) {
2070 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2071 }
2072
2073 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2074 appSearchData, sourceBounds);
2075 if (clearTextImmediately) {
2076 clearTypedText();
2077 }
2078 }
2079
2080 /**
2081 * Start a text search.
2082 *
2083 * @return {@code true} if the search will start immediately, so any further keypresses
2084 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2085 * to buffer keypresses.
2086 */
2087 public boolean startSearch(String initialQuery,
2088 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2089 startGlobalSearch(initialQuery, selectInitialQuery,
2090 appSearchData, sourceBounds);
2091 return false;
2092 }
2093
2094 /**
2095 * Starts the global search activity. This code is a copied from SearchManager
2096 */
2097 private void startGlobalSearch(String initialQuery,
2098 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2099 final SearchManager searchManager =
2100 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2101 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2102 if (globalSearchActivity == null) {
2103 Log.w(TAG, "No global search activity found.");
2104 return;
2105 }
2106 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2107 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2108 intent.setComponent(globalSearchActivity);
2109 // Make sure that we have a Bundle to put source in
2110 if (appSearchData == null) {
2111 appSearchData = new Bundle();
2112 } else {
2113 appSearchData = new Bundle(appSearchData);
2114 }
2115 // Set source to package name of app that starts global search, if not set already.
2116 if (!appSearchData.containsKey("source")) {
2117 appSearchData.putString("source", getPackageName());
2118 }
2119 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2120 if (!TextUtils.isEmpty(initialQuery)) {
2121 intent.putExtra(SearchManager.QUERY, initialQuery);
2122 }
2123 if (selectInitialQuery) {
2124 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2125 }
2126 intent.setSourceBounds(sourceBounds);
2127 try {
2128 startActivity(intent);
2129 } catch (ActivityNotFoundException ex) {
2130 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2131 }
2132 }
2133
2134 public boolean isOnCustomContent() {
2135 return mWorkspace.isOnOrMovingToCustomContent();
2136 }
2137
2138 @Override
2139 public boolean onPrepareOptionsMenu(Menu menu) {
2140 super.onPrepareOptionsMenu(menu);
2141 if (!isOnCustomContent()) {
2142 // Close any open folders
2143 closeFolder();
2144 // Stop resizing any widgets
2145 mWorkspace.exitWidgetResizeMode();
2146 if (!mWorkspace.isInOverviewMode()) {
2147 // Show the overview mode
2148 showOverviewMode(true);
2149 } else {
2150 showWorkspace(true);
2151 }
2152 }
2153 return false;
2154 }
2155
2156 @Override
2157 public boolean onSearchRequested() {
2158 startSearch(null, false, null, true);
2159 // Use a custom animation for launching search
2160 return true;
2161 }
2162
2163 public boolean isWorkspaceLocked() {
2164 return mWorkspaceLoading || mWaitingForResult;
2165 }
2166
2167 public boolean isWorkspaceLoading() {
2168 return mWorkspaceLoading;
2169 }
2170
2171 private void setWorkspaceLoading(boolean value) {
2172 boolean isLocked = isWorkspaceLocked();
2173 mWorkspaceLoading = value;
2174 if (isLocked != isWorkspaceLocked()) {
2175 onWorkspaceLockedChanged();
2176 }
2177 }
2178
2179 private void setWaitingForResult(boolean value) {
2180 boolean isLocked = isWorkspaceLocked();
2181 mWaitingForResult = value;
2182 if (isLocked != isWorkspaceLocked()) {
2183 onWorkspaceLockedChanged();
2184 }
2185 }
2186
2187 protected void onWorkspaceLockedChanged() { }
2188
2189 private void resetAddInfo() {
2190 mPendingAddInfo.container = ItemInfo.NO_ID;
2191 mPendingAddInfo.screenId = -1;
2192 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2193 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2194 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2195 mPendingAddInfo.dropPos = null;
2196 }
2197
2198 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2199 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo) {
2200 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2201 }
2202
2203 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2204 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo, int
2205 delay) {
2206 if (appWidgetInfo.configure != null) {
2207 mPendingAddWidgetInfo = appWidgetInfo;
2208 mPendingAddWidgetId = appWidgetId;
2209
2210 // Launch over to configure widget, if needed
2211 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2212 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2213
2214 } else {
2215 // Otherwise just add it
2216 Runnable onComplete = new Runnable() {
2217 @Override
2218 public void run() {
2219 // Exit spring loaded mode if necessary after adding the widget
2220 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2221 null);
2222 }
2223 };
2224 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2225 appWidgetInfo);
2226 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2227 }
2228 }
2229
2230 protected void moveToCustomContentScreen(boolean animate) {
2231 // Close any folders that may be open.
2232 closeFolder();
2233 mWorkspace.moveToCustomContentScreen(animate);
2234 }
2235 /**
2236 * Process a shortcut drop.
2237 *
2238 * @param componentName The name of the component
2239 * @param screenId The ID of the screen where it should be added
2240 * @param cell The cell it should be added to, optional
2241 * @param position The location on the screen where it was dropped, optional
2242 */
2243 void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2244 int[] cell, int[] loc) {
2245 resetAddInfo();
2246 mPendingAddInfo.container = container;
2247 mPendingAddInfo.screenId = screenId;
2248 mPendingAddInfo.dropPos = loc;
2249
2250 if (cell != null) {
2251 mPendingAddInfo.cellX = cell[0];
2252 mPendingAddInfo.cellY = cell[1];
2253 }
2254
2255 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2256 createShortcutIntent.setComponent(componentName);
2257 processShortcut(createShortcutIntent);
2258 }
2259
2260 /**
2261 * Process a widget drop.
2262 *
2263 * @param info The PendingAppWidgetInfo of the widget being added.
2264 * @param screenId The ID of the screen where it should be added
2265 * @param cell The cell it should be added to, optional
2266 * @param position The location on the screen where it was dropped, optional
2267 */
2268 void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2269 int[] cell, int[] span, int[] loc) {
2270 resetAddInfo();
2271 mPendingAddInfo.container = info.container = container;
2272 mPendingAddInfo.screenId = info.screenId = screenId;
2273 mPendingAddInfo.dropPos = loc;
2274 mPendingAddInfo.minSpanX = info.minSpanX;
2275 mPendingAddInfo.minSpanY = info.minSpanY;
2276
2277 if (cell != null) {
2278 mPendingAddInfo.cellX = cell[0];
2279 mPendingAddInfo.cellY = cell[1];
2280 }
2281 if (span != null) {
2282 mPendingAddInfo.spanX = span[0];
2283 mPendingAddInfo.spanY = span[1];
2284 }
2285
2286 AppWidgetHostView hostView = info.boundWidget;
2287 int appWidgetId;
2288 if (hostView != null) {
2289 appWidgetId = hostView.getAppWidgetId();
2290 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2291 } else {
2292 // In this case, we either need to start an activity to get permission to bind
2293 // the widget, or we need to start an activity to configure the widget, or both.
2294 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2295 Bundle options = info.bindOptions;
2296
2297 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2298 appWidgetId, info.info, options);
2299 if (success) {
2300 addAppWidgetImpl(appWidgetId, info, null, info.info);
2301 } else {
2302 mPendingAddWidgetInfo = info.info;
2303 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2304 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2305 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2306 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2307 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2308 // TODO: we need to make sure that this accounts for the options bundle.
2309 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2310 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2311 }
2312 }
2313 }
2314
2315 void processShortcut(Intent intent) {
2316 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2317 }
2318
2319 void processWallpaper(Intent intent) {
2320 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2321 }
2322
2323 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2324 int cellY) {
2325 final FolderInfo folderInfo = new FolderInfo();
2326 folderInfo.title = getText(R.string.folder_name);
2327
2328 // Update the model
2329 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
2330 false);
2331 sFolders.put(folderInfo.id, folderInfo);
2332
2333 // Create the view
2334 FolderIcon newFolder =
2335 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2336 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2337 isWorkspaceLocked());
2338 // Force measure the new folder icon
2339 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2340 parent.getShortcutsAndWidgets().measureChild(newFolder);
2341 return newFolder;
2342 }
2343
2344 void removeFolder(FolderInfo folder) {
2345 sFolders.remove(folder.id);
2346 }
2347
2348 protected ComponentName getWallpaperPickerComponent() {
2349 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
2350 }
2351
2352 /**
2353 * Registers various content observers. The current implementation registers
2354 * only a favorites observer to keep track of the favorites applications.
2355 */
2356 private void registerContentObservers() {
2357 ContentResolver resolver = getContentResolver();
2358 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2359 true, mWidgetObserver);
2360 }
2361
2362 @Override
2363 public boolean dispatchKeyEvent(KeyEvent event) {
2364 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2365 switch (event.getKeyCode()) {
2366 case KeyEvent.KEYCODE_HOME:
2367 return true;
2368 case KeyEvent.KEYCODE_VOLUME_DOWN:
2369 if (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2370 dumpState();
2371 return true;
2372 }
2373 break;
2374 }
2375 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2376 switch (event.getKeyCode()) {
2377 case KeyEvent.KEYCODE_HOME:
2378 return true;
2379 }
2380 }
2381
2382 return super.dispatchKeyEvent(event);
2383 }
2384
2385 @Override
2386 public void onBackPressed() {
2387 if (isAllAppsVisible()) {
2388 if (mAppsCustomizeContent.getContentType() ==
2389 AppsCustomizePagedView.ContentType.Applications) {
2390 showWorkspace(true);
2391 } else {
2392 showOverviewMode(true);
2393 }
2394 } else if (mWorkspace.isInOverviewMode()) {
2395 mWorkspace.exitOverviewMode(true);
2396 } else if (mWorkspace.getOpenFolder() != null) {
2397 Folder openFolder = mWorkspace.getOpenFolder();
2398 if (openFolder.isEditingName()) {
2399 openFolder.dismissEditingName();
2400 } else {
2401 closeFolder();
2402 }
2403 } else {
2404 mWorkspace.exitWidgetResizeMode();
2405
2406 // Back button is a no-op here, but give at least some feedback for the button press
2407 mWorkspace.showOutlinesTemporarily();
2408 }
2409 }
2410
2411 /**
2412 * Re-listen when widgets are reset.
2413 */
2414 private void onAppWidgetReset() {
2415 if (mAppWidgetHost != null) {
2416 mAppWidgetHost.startListening();
2417 }
2418 }
2419
2420 /**
2421 * Launches the intent referred by the clicked shortcut.
2422 *
2423 * @param v The view representing the clicked shortcut.
2424 */
2425 public void onClick(View v) {
2426 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2427 // view has detached (it's possible for this to happen if the view is removed mid touch).
2428 if (v.getWindowToken() == null) {
2429 return;
2430 }
2431
2432 if (!mWorkspace.isFinishedSwitchingState()) {
2433 return;
2434 }
2435
2436 if (v instanceof Workspace) {
2437 if (mWorkspace.isInOverviewMode()) {
2438 mWorkspace.exitOverviewMode(true);
2439 }
2440 return;
2441 }
2442
2443 if (v instanceof CellLayout) {
2444 if (mWorkspace.isInOverviewMode()) {
2445 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2446 }
2447 }
2448
2449 Object tag = v.getTag();
2450 if (tag instanceof ShortcutInfo) {
2451 onClickAppShortcut(v);
2452 } else if (tag instanceof FolderInfo) {
2453 if (v instanceof FolderIcon) {
2454 onClickFolderIcon(v);
2455 }
2456 } else if (v == mAllAppsButton) {
2457 onClickAllAppsButton(v);
2458 } else if (tag instanceof AppInfo) {
2459 startAppShortcutOrInfoActivity(v);
2460 } else if (tag instanceof LauncherAppWidgetInfo) {
2461 if (v instanceof PendingAppWidgetHostView) {
2462 onClickPendingWidget((PendingAppWidgetHostView) v);
2463 }
2464 }
2465 }
2466
2467 public void onClickPagedViewIcon(View v) {
2468 startAppShortcutOrInfoActivity(v);
2469 }
2470
2471 public boolean onTouch(View v, MotionEvent event) {
2472 return false;
2473 }
2474
2475 /**
2476 * Event handler for the app widget view which has not fully restored.
2477 */
2478 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2479 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2480 if (v.isReadyForClickSetup()) {
2481 int widgetId = info.appWidgetId;
2482 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2483 if (appWidgetInfo != null) {
2484 mPendingAddWidgetInfo = appWidgetInfo;
2485 mPendingAddInfo.copyFrom(info);
2486 mPendingAddWidgetId = widgetId;
2487
2488 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2489 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2490 }
2491 } else if (info.installProgress < 0) {
2492 // The install has not been queued
2493 final String packageName = info.providerName.getPackageName();
2494 showBrokenAppInstallDialog(packageName,
2495 new DialogInterface.OnClickListener() {
2496 public void onClick(DialogInterface dialog, int id) {
2497 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2498 }
2499 });
2500 } else {
2501 // Download has started.
2502 final String packageName = info.providerName.getPackageName();
2503 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2504 }
2505 }
2506
2507 /**
2508 * Event handler for the search button
2509 *
2510 * @param v The view that was clicked.
2511 */
2512 public void onClickSearchButton(View v) {
2513 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2514
2515 onSearchRequested();
2516 }
2517
2518 /**
2519 * Event handler for the voice button
2520 *
2521 * @param v The view that was clicked.
2522 */
2523 public void onClickVoiceButton(View v) {
2524 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2525
2526 startVoice();
2527 }
2528
2529 public void startVoice() {
2530 try {
2531 final SearchManager searchManager =
2532 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2533 ComponentName activityName = searchManager.getGlobalSearchActivity();
2534 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2535 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2536 if (activityName != null) {
2537 intent.setPackage(activityName.getPackageName());
2538 }
2539 startActivity(null, intent, "onClickVoiceButton");
2540 } catch (ActivityNotFoundException e) {
2541 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2542 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2543 startActivitySafely(null, intent, "onClickVoiceButton");
2544 }
2545 }
2546
2547 /**
2548 * Event handler for the "grid" button that appears on the home screen, which
2549 * enters all apps mode.
2550 *
2551 * @param v The view that was clicked.
2552 */
2553 protected void onClickAllAppsButton(View v) {
2554 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2555 if (isAllAppsVisible()) {
2556 showWorkspace(true);
2557 } else {
2558 showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false);
2559 }
2560 }
2561
2562 private void showBrokenAppInstallDialog(final String packageName,
2563 DialogInterface.OnClickListener onSearchClickListener) {
2564 new AlertDialog.Builder(this)
2565 .setTitle(R.string.abandoned_promises_title)
2566 .setMessage(R.string.abandoned_promise_explanation)
2567 .setPositiveButton(R.string.abandoned_search, onSearchClickListener)
2568 .setNeutralButton(R.string.abandoned_clean_this,
2569 new DialogInterface.OnClickListener() {
2570 public void onClick(DialogInterface dialog, int id) {
2571 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2572 mWorkspace.removeAbandonedPromise(packageName, user);
2573 }
2574 })
2575 .create().show();
2576 return;
2577 }
2578
2579 /**
2580 * Event handler for an app shortcut click.
2581 *
2582 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2583 */
2584 protected void onClickAppShortcut(final View v) {
2585 if (LOGD) Log.d(TAG, "onClickAppShortcut");
2586 Object tag = v.getTag();
2587 if (!(tag instanceof ShortcutInfo)) {
2588 throw new IllegalArgumentException("Input must be a Shortcut");
2589 }
2590
2591 // Open shortcut
2592 final ShortcutInfo shortcut = (ShortcutInfo) tag;
2593
2594 if (shortcut.isDisabled != 0) {
2595 int error = R.string.activity_not_available;
2596 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2597 error = R.string.safemode_shortcut_error;
2598 }
2599 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2600 return;
2601 }
2602
2603 final Intent intent = shortcut.intent;
2604
2605 // Check for special shortcuts
2606 if (intent.getComponent() != null) {
2607 final String shortcutClass = intent.getComponent().getClassName();
2608
2609 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2610 MemoryDumpActivity.startDump(this);
2611 return;
2612 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2613 toggleShowWeightWatcher();
2614 return;
2615 }
2616 }
2617
2618 // Check for abandoned promise
2619 if ((v instanceof BubbleTextView)
2620 && shortcut.isPromise()
2621 && !shortcut.hasStatusFlag(ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE)) {
2622 showBrokenAppInstallDialog(
2623 shortcut.getTargetComponent().getPackageName(),
2624 new DialogInterface.OnClickListener() {
2625 public void onClick(DialogInterface dialog, int id) {
2626 startAppShortcutOrInfoActivity(v);
2627 }
2628 });
2629 return;
2630 }
2631
2632 // Start activities
2633 startAppShortcutOrInfoActivity(v);
2634 }
2635
2636 private void startAppShortcutOrInfoActivity(View v) {
2637 Object tag = v.getTag();
2638 final ShortcutInfo shortcut;
2639 final Intent intent;
2640 if (tag instanceof ShortcutInfo) {
2641 shortcut = (ShortcutInfo) tag;
2642 intent = shortcut.intent;
2643 int[] pos = new int[2];
2644 v.getLocationOnScreen(pos);
2645 intent.setSourceBounds(new Rect(pos[0], pos[1],
2646 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2647
2648 } else if (tag instanceof AppInfo) {
2649 shortcut = null;
2650 intent = ((AppInfo) tag).intent;
2651 } else {
2652 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2653 }
2654
2655 boolean success = startActivitySafely(v, intent, tag);
2656 mStats.recordLaunch(intent, shortcut);
2657
2658 if (success && v instanceof BubbleTextView) {
2659 mWaitingForResume = (BubbleTextView) v;
2660 mWaitingForResume.setStayPressed(true);
2661 }
2662 }
2663
2664 /**
2665 * Event handler for a folder icon click.
2666 *
2667 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2668 */
2669 protected void onClickFolderIcon(View v) {
2670 if (LOGD) Log.d(TAG, "onClickFolder");
2671 if (!(v instanceof FolderIcon)){
2672 throw new IllegalArgumentException("Input must be a FolderIcon");
2673 }
2674
2675 FolderIcon folderIcon = (FolderIcon) v;
2676 final FolderInfo info = folderIcon.getFolderInfo();
2677 Folder openFolder = mWorkspace.getFolderForTag(info);
2678
2679 // If the folder info reports that the associated folder is open, then verify that
2680 // it is actually opened. There have been a few instances where this gets out of sync.
2681 if (info.opened && openFolder == null) {
2682 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2683 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2684 info.opened = false;
2685 }
2686
2687 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2688 // Close any open folder
2689 closeFolder();
2690 // Open the requested folder
2691 openFolder(folderIcon);
2692 } else {
2693 // Find the open folder...
2694 int folderScreen;
2695 if (openFolder != null) {
2696 folderScreen = mWorkspace.getPageForView(openFolder);
2697 // .. and close it
2698 closeFolder(openFolder);
2699 if (folderScreen != mWorkspace.getCurrentPage()) {
2700 // Close any folder open on the current screen
2701 closeFolder();
2702 // Pull the folder onto this screen
2703 openFolder(folderIcon);
2704 }
2705 }
2706 }
2707 }
2708
2709 /**
2710 * Event handler for the (Add) Widgets button that appears after a long press
2711 * on the home screen.
2712 */
2713 protected void onClickAddWidgetButton(View view) {
2714 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2715 showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
2716 }
2717
2718 /**
2719 * Event handler for the wallpaper picker button that appears after a long press
2720 * on the home screen.
2721 */
2722 protected void onClickWallpaperPicker(View v) {
2723 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2724 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
2725 pickWallpaper.setComponent(getWallpaperPickerComponent());
2726 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
2727 }
2728
2729 /**
2730 * Event handler for a click on the settings button that appears after a long press
2731 * on the home screen.
2732 */
2733 protected void onClickSettingsButton(View v) {
2734 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2735 }
2736
2737 public void onTouchDownAllAppsButton(View v) {
2738 // Provide the same haptic feedback that the system offers for virtual keys.
2739 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2740 }
2741
2742 public void performHapticFeedbackOnTouchDown(View v) {
2743 // Provide the same haptic feedback that the system offers for virtual keys.
2744 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2745 }
2746
2747 public View.OnTouchListener getHapticFeedbackTouchListener() {
2748 if (mHapticFeedbackTouchListener == null) {
2749 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2750 @Override
2751 public boolean onTouch(View v, MotionEvent event) {
2752 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2753 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2754 }
2755 return false;
2756 }
2757 };
2758 }
2759 return mHapticFeedbackTouchListener;
2760 }
2761
2762 public void onDragStarted(View view) {}
2763
2764 /**
2765 * Called when the user stops interacting with the launcher.
2766 * This implies that the user is now on the homescreen and is not doing housekeeping.
2767 */
2768 protected void onInteractionEnd() {}
2769
2770 /**
2771 * Called when the user starts interacting with the launcher.
2772 * The possible interactions are:
2773 * - open all apps
2774 * - reorder an app shortcut, or a widget
2775 * - open the overview mode.
2776 * This is a good time to stop doing things that only make sense
2777 * when the user is on the homescreen and not doing housekeeping.
2778 */
2779 protected void onInteractionBegin() {}
2780
2781 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2782 String packageName = componentName.getPackageName();
2783 try {
2784 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2785 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2786 launcherApps.showAppDetailsForProfile(componentName, user);
2787 } catch (SecurityException e) {
2788 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2789 Log.e(TAG, "Launcher does not have permission to launch settings");
2790 } catch (ActivityNotFoundException e) {
2791 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2792 Log.e(TAG, "Unable to launch settings");
2793 }
2794 }
2795
2796 // returns true if the activity was started
2797 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2798 UserHandleCompat user) {
2799 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2800 // System applications cannot be installed. For now, show a toast explaining that.
2801 // We may give them the option of disabling apps this way.
2802 int messageId = R.string.uninstall_system_app_text;
2803 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2804 return false;
2805 } else {
2806 String packageName = componentName.getPackageName();
2807 String className = componentName.getClassName();
2808 Intent intent = new Intent(
2809 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2810 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2811 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2812 if (user != null) {
2813 user.addToIntent(intent, Intent.EXTRA_USER);
2814 }
2815 startActivity(intent);
2816 return true;
2817 }
2818 }
2819
2820 boolean startActivity(View v, Intent intent, Object tag) {
2821 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2822 try {
2823 // Only launch using the new animation if the shortcut has not opted out (this is a
2824 // private contract between launcher and may be ignored in the future).
2825 boolean useLaunchAnimation = (v != null) &&
2826 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2827 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2828 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2829
2830 UserHandleCompat user = null;
2831 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2832 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2833 user = userManager.getUserForSerialNumber(serialNumber);
2834 }
2835
2836 Bundle optsBundle = null;
2837 if (useLaunchAnimation) {
2838 ActivityOptions opts = Utilities.isLmpOrAbove() ?
2839 ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim)🔵
2840 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
2841 optsBundle = opts.toBundle();
2842 }
2843
2844 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2845 // Could be launching some bookkeeping activity
2846 startActivity(intent, optsBundle);
2847 } else {
2848 // TODO Component can be null when shortcuts are supported for secondary user
2849 launcherApps.startActivityForProfile(intent.getComponent(), user,
2850 intent.getSourceBounds(), optsBundle);
2851 }
2852 return true;
2853 } catch (SecurityException e) {
2854 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2855 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2856 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2857 "or use the exported attribute for this activity. "
2858 + "tag="+ tag + " intent=" + intent, e);
2859 }
2860 return false;
2861 }
2862
2863 boolean startActivitySafely(View v, Intent intent, Object tag) {
2864 boolean success = false;
2865 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2866 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2867 return false;
2868 }
2869 try {
2870 success = startActivity(v, intent, tag);
2871 } catch (ActivityNotFoundException e) {
2872 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2873 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2874 }
2875 return success;
2876 }
2877
2878 /**
2879 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2880 * in the DragLayer in the exact absolute location of the original FolderIcon.
2881 */
2882 private void copyFolderIconToImage(FolderIcon fi) {
2883 final int width = fi.getMeasuredWidth();
2884 final int height = fi.getMeasuredHeight();
2885
2886 // Lazy load ImageView, Bitmap and Canvas
2887 if (mFolderIconImageView == null) {
2888 mFolderIconImageView = new ImageView(this);
2889 }
2890 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
2891 mFolderIconBitmap.getHeight() != height) {
2892 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
2893 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
2894 }
2895
2896 DragLayer.LayoutParams lp;
2897 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
2898 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
2899 } else {
2900 lp = new DragLayer.LayoutParams(width, height);
2901 }
2902
2903 // The layout from which the folder is being opened may be scaled, adjust the starting
2904 // view size by this scale factor.
2905 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
2906 lp.customPosition = true;
2907 lp.x = mRectForFolderAnimation.left;
2908 lp.y = mRectForFolderAnimation.top;
2909 lp.width = (int) (scale * width);
2910 lp.height = (int) (scale * height);
2911
2912 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
2913 fi.draw(mFolderIconCanvas);
2914 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
2915 if (fi.getFolder() != null) {
2916 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
2917 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
2918 }
2919 // Just in case this image view is still in the drag layer from a previous animation,
2920 // we remove it and re-add it.
2921 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
2922 mDragLayer.removeView(mFolderIconImageView);
2923 }
2924 mDragLayer.addView(mFolderIconImageView, lp);
2925 if (fi.getFolder() != null) {
2926 fi.getFolder().bringToFront();
2927 }
2928 }
2929
2930 private void growAndFadeOutFolderIcon(FolderIcon fi) {
2931 if (fi == null) return;
2932 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
2933 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
2934 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
2935
2936 FolderInfo info = (FolderInfo) fi.getTag();
2937 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2938 CellLayout cl = (CellLayout) fi.getParent().getParent();
2939 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
2940 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
2941 }
2942
2943 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
2944 copyFolderIconToImage(fi);
2945 fi.setVisibility(View.INVISIBLE);
2946
2947 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
2948 scaleX, scaleY);
2949 if (Utilities.isLmpOrAbove()) {
2950 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
2951 }
2952 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
2953 oa.start();
2954 }
2955
2956 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
2957 if (fi == null) return;
2958 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
2959 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
2960 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
2961
2962 final CellLayout cl = (CellLayout) fi.getParent().getParent();
2963
2964 // We remove and re-draw the FolderIcon in-case it has changed
2965 mDragLayer.removeView(mFolderIconImageView);
2966 copyFolderIconToImage(fi);
2967 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
2968 scaleX, scaleY);
2969 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
2970 oa.addListener(new AnimatorListenerAdapter() {
2971 @Override
2972 public void onAnimationEnd(Animator animation) {
2973 if (cl != null) {
2974 cl.clearFolderLeaveBehind();
2975 // Remove the ImageView copy of the FolderIcon and make the original visible.
2976 mDragLayer.removeView(mFolderIconImageView);
2977 fi.setVisibility(View.VISIBLE);
2978 }
2979 }
2980 });
2981 oa.start();
2982 }
2983
2984 /**
2985 * Opens the user folder described by the specified tag. The opening of the folder
2986 * is animated relative to the specified View. If the View is null, no animation
2987 * is played.
2988 *
2989 * @param folderInfo The FolderInfo describing the folder to open.
2990 */
2991 public void openFolder(FolderIcon folderIcon) {
2992 Folder folder = folderIcon.getFolder();
2993 FolderInfo info = folder.mInfo;
2994
2995 info.opened = true;
2996
2997 // Just verify that the folder hasn't already been added to the DragLayer.
2998 // There was a one-off crash where the folder had a parent already.
2999 if (folder.getParent() == null) {
3000 mDragLayer.addView(folder);
3001 mDragController.addDropTarget((DropTarget) folder);
3002 } else {
3003 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3004 folder.getParent() + ").");
3005 }
3006 folder.animateOpen();
3007 growAndFadeOutFolderIcon(folderIcon);
3008
3009 // Notify the accessibility manager that this folder "window" has appeared and occluded
3010 // the workspace items
3011 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3012 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3013 }
3014
3015 public void closeFolder() {
3016 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3017 if (folder != null) {
3018 if (folder.isEditingName()) {
3019 folder.dismissEditingName();
3020 }
3021 closeFolder(folder);
3022 }
3023 }
3024
3025 void closeFolder(Folder folder) {
3026 folder.getInfo().opened = false;
3027
3028 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3029 if (parent != null) {
3030 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3031 shrinkAndFadeInFolderIcon(fi);
3032 }
3033 folder.animateClosed();
3034
3035 // Notify the accessibility manager that this folder "window" has disappeard and no
3036 // longer occludeds the workspace items
3037 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3038 }
3039
3040 public boolean onLongClick(View v) {
3041 if (!isDraggingEnabled()) return false;
3042 if (isWorkspaceLocked()) return false;
3043 if (mState != State.WORKSPACE) return false;
3044
3045 if (v instanceof Workspace) {
3046 if (!mWorkspace.isInOverviewMode()) {
3047 if (mWorkspace.enterOverviewMode()) {
3048 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3049 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3050 return true;
3051 } else {
3052 return false;
3053 }
3054 } else {
3055 return false;
3056 }
3057 }
3058
3059 CellLayout.CellInfo longClickCellInfo = null;
3060 View itemUnderLongClick = null;
3061 if (v.getTag() instanceof ItemInfo) {
3062 ItemInfo info = (ItemInfo) v.getTag();
3063 longClickCellInfo = new CellLayout.CellInfo(v, info);;
3064 itemUnderLongClick = longClickCellInfo.cell;
3065 resetAddInfo();
3066 }
3067
3068 // The hotseat touch handling does not go through Workspace, and we always allow long press
3069 // on hotseat items.
3070 final boolean inHotseat = isHotseatLayout(v);
3071 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3072 if (allowLongPress && !mDragController.isDragging()) {
3073 if (itemUnderLongClick == null) {
3074 // User long pressed on empty space
3075 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3076 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3077 if (mWorkspace.isInOverviewMode()) {
3078 mWorkspace.startReordering(v);
3079 } else {
3080 mWorkspace.enterOverviewMode();
3081 }
3082 } else {
3083 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3084 mHotseat.getOrderInHotseat(
3085 longClickCellInfo.cellX,
3086 longClickCellInfo.cellY));
3087 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3088 // User long pressed on an item
3089 mWorkspace.startDrag(longClickCellInfo);
3090 }
3091 }
3092 }
3093 return true;
3094 }
3095
3096 boolean isHotseatLayout(View layout) {
3097 return mHotseat != null && layout != null &&
3098 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3099 }
3100
3101 /**
3102 * Returns the CellLayout of the specified container at the specified screen.
3103 */
3104 CellLayout getCellLayout(long container, long screenId) {
3105 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3106 if (mHotseat != null) {
3107 return mHotseat.getLayout();
3108 } else {
3109 return null;
3110 }
3111 } else {
3112 return (CellLayout) mWorkspace.getScreenWithId(screenId);
3113 }
3114 }
3115
3116 public boolean isAllAppsVisible() {
3117 return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
3118 }
3119
3120 private void setWorkspaceBackground(boolean workspace) {
3121 mLauncherView.setBackground(workspace ?
3122 mWorkspaceBackgroundDrawable : null);
3123 }
3124
3125 protected void changeWallpaperVisiblity(boolean visible) {
3126 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3127 int curflags = getWindow().getAttributes().flags
3128 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3129 if (wpflags != curflags) {
3130 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3131 }
3132 setWorkspaceBackground(visible);
3133 }
3134
3135 private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
3136 if (v instanceof LauncherTransitionable) {
3137 ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
3138 }
3139 }
3140
3141 private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
3142 if (v instanceof LauncherTransitionable) {
3143 ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
3144 }
3145
3146 // Update the workspace transition step as well
3147 dispatchOnLauncherTransitionStep(v, 0f);
3148 }
3149
3150 private void dispatchOnLauncherTransitionStep(View v, float t) {
3151 if (v instanceof LauncherTransitionable) {
3152 ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
3153 }
3154 }
3155
3156 private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
3157 if (v instanceof LauncherTransitionable) {
3158 ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
3159 }
3160
3161 // Update the workspace transition step as well
3162 dispatchOnLauncherTransitionStep(v, 1f);
3163 }
3164
3165 /**
3166 * Things to test when changing the following seven functions.
3167 * - Home from workspace
3168 * - from center screen
3169 * - from other screens
3170 * - Home from all apps
3171 * - from center screen
3172 * - from other screens
3173 * - Back from all apps
3174 * - from center screen
3175 * - from other screens
3176 * - Launch app from workspace and quit
3177 * - with back
3178 * - with home
3179 * - Launch app from all apps and quit
3180 * - with back
3181 * - with home
3182 * - Go to a screen that's not the default, then all
3183 * apps, and launch and app, and go back
3184 * - with back
3185 * -with home
3186 * - On workspace, long press power and go back
3187 * - with back
3188 * - with home
3189 * - On all apps, long press power and go back
3190 * - with back
3191 * - with home
3192 * - On workspace, power off
3193 * - On all apps, power off
3194 * - Launch an app and turn off the screen while in that app
3195 * - Go back with home key
3196 * - Go back with back key TODO: make this not go to workspace
3197 * - From all apps
3198 * - From workspace
3199 * - Enter and exit car mode (becuase it causes an extra configuration changed)
3200 * - From all apps
3201 * - From the center workspace
3202 * - From another workspace
3203 */
3204
3205 /**
3206 * Zoom the camera out from the workspace to reveal 'toView'.
3207 * Assumes that the view to show is anchored at either the very top or very bottom
3208 * of the screen.
3209 */
3210 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
3211 AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
3212 showAppsCustomizeHelper(animated, springLoaded, contentType);
3213 }
3214
3215 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
3216 final AppsCustomizePagedView.ContentType contentType) {
3217 if (mStateAnimation != null) {
3218 mStateAnimation.setDuration(0);
3219 mStateAnimation.cancel();
3220 mStateAnimation = null;
3221 }
3222
3223 boolean material = Utilities.isLmpOrAbove();
3224
3225 final Resources res = getResources();
3226
3227 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
3228 final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
3229 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
3230 final int itemsAlphaStagger =
3231 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
3232
3233 final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
3234 final View fromView = mWorkspace;
3235 final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
3236
3237 final ArrayList<View> layerViews = new ArrayList<View>();
3238
3239 Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ?
3240 Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN;
3241 Animator workspaceAnim =
3242 mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews);
3243 if (!LauncherAppState.isDisableAllApps()
3244 || contentType == AppsCustomizePagedView.ContentType.Widgets) {
3245 // Set the content type for the all apps/widgets space
3246 mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
3247 }
3248
3249 // If for some reason our views aren't initialized, don't animate
3250 boolean initialized = getAllAppsButton() != null;
3251
3252 if (animated && initialized) {
3253 mStateAnimation = LauncherAnimUtils.createAnimatorSet();
3254 final AppsCustomizePagedView content = (AppsCustomizePagedView)
3255 toView.findViewById(R.id.apps_customize_pane_content);
3256
3257 final View page = content.getPageAt(content.getCurrentPage());
3258 final View revealView = toView.findViewById(R.id.fake_page);
3259
3260 final float initialPanelAlpha = 1f;
3261
3262 final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets;
3263 if (isWidgetTray) {
3264 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
3265 } else {
3266 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
3267 }
3268
3269 // Hide the real page background, and swap in the fake one
3270 content.setPageBackgroundsVisible(false);
3271 revealView.setVisibility(View.VISIBLE);
3272 // We need to hide this view as the animation start will be posted.
3273 revealView.setAlpha(0);
3274
3275 int width = revealView.getMeasuredWidth();
3276 int height = revealView.getMeasuredHeight();
3277 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
3278
3279 revealView.setTranslationY(0);
3280 revealView.setTranslationX(0);
3281
3282 // Get the y delta between the center of the page and the center of the all apps button
3283 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
3284 getAllAppsButton(), null);
3285
3286 float alpha = 0;
3287 float xDrift = 0;
3288 float yDrift = 0;
3289 if (material) {
3290 alpha = isWidgetTray ? 0.3f : 1f;
3291 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
3292 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
3293 } else {
3294 yDrift = 2 * height / 3;
3295 xDrift = 0;
3296 }
3297 final float initAlpha = alpha;
3298
3299 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3300 layerViews.add(revealView);
3301 PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f);
3302 PropertyValuesHolder panelDriftY =
3303 PropertyValuesHolder.ofFloat("translationY", yDrift, 0);
3304 PropertyValuesHolder panelDriftX =
3305 PropertyValuesHolder.ofFloat("translationX", xDrift, 0);
3306
3307 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
3308 panelAlpha, panelDriftY, panelDriftX);
3309
3310 panelAlphaAndDrift.setDuration(revealDuration);
3311 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
3312
3313 mStateAnimation.play(panelAlphaAndDrift);
3314
3315 if (page != null) {
3316 page.setVisibility(View.VISIBLE);
3317 page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3318 layerViews.add(page);
3319
3320 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0);
3321 page.setTranslationY(yDrift);
3322 pageDrift.setDuration(revealDuration);
3323 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
3324 pageDrift.setStartDelay(itemsAlphaStagger);
3325 mStateAnimation.play(pageDrift);
3326
3327 page.setAlpha(0f);
3328 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f);
3329 itemsAlpha.setDuration(revealDuration);
3330 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
3331 itemsAlpha.setStartDelay(itemsAlphaStagger);
3332 mStateAnimation.play(itemsAlpha);
3333 }
3334
3335 View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator);
3336 pageIndicators.setAlpha(0.01f);
3337 ObjectAnimator indicatorsAlpha =
3338 ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f);
3339 indicatorsAlpha.setDuration(revealDuration);
3340 mStateAnimation.play(indicatorsAlpha);
3341
3342 if (material) {
3343 final View allApps = getAllAppsButton();
3344 int allAppsButtonSize = LauncherAppState.getInstance().
3345 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
3346 float startRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
3347 Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2,
3348 height / 2, startRadius, revealRadius);
3349 reveal.setDuration(revealDuration);
3350 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
3351
3352 reveal.addListener(new AnimatorListenerAdapter() {
3353 public void onAnimationStart(Animator animation) {
3354 if (!isWidgetTray) {
3355 allApps.setVisibility(View.INVISIBLE);
3356 }
3357 }
3358 public void onAnimationEnd(Animator animation) {
3359 if (!isWidgetTray) {
3360 allApps.setVisibility(View.VISIBLE);
3361 }
3362 }
3363 });
3364 mStateAnimation.play(reveal);
3365 }
3366
3367 mStateAnimation.addListener(new AnimatorListenerAdapter() {
3368 @Override
3369 public void onAnimationEnd(Animator animation) {
3370 dispatchOnLauncherTransitionEnd(fromView, animated, false);
3371 dispatchOnLauncherTransitionEnd(toView, animated, false);
3372
3373 revealView.setVisibility(View.INVISIBLE);
3374 revealView.setLayerType(View.LAYER_TYPE_NONE, null);
3375 if (page != null) {
3376 page.setLayerType(View.LAYER_TYPE_NONE, null);
3377 }
3378 content.setPageBackgroundsVisible(true);
3379
3380 // Hide the search bar
3381 if (mSearchDropTargetBar != null) {
3382 mSearchDropTargetBar.hideSearchBar(false);
3383 }
3384 }
3385
3386 });
3387
3388 if (workspaceAnim != null) {
3389 mStateAnimation.play(workspaceAnim);
3390 }
3391
3392 dispatchOnLauncherTransitionPrepare(fromView, animated, false);
3393 dispatchOnLauncherTransitionPrepare(toView, animated, false);
3394 final AnimatorSet stateAnimation = mStateAnimation;
3395 final Runnable startAnimRunnable = new Runnable() {
3396 public void run() {
3397 // Check that mStateAnimation hasn't changed while
3398 // we waited for a layout/draw pass
3399 if (mStateAnimation != stateAnimation)
3400 return;
3401 dispatchOnLauncherTransitionStart(fromView, animated, false);
3402 dispatchOnLauncherTransitionStart(toView, animated, false);
3403
3404 revealView.setAlpha(initAlpha);
3405 if (Utilities.isLmpOrAbove()) {
3406 for (int i = 0; i < layerViews.size(); i++) {
3407 View v = layerViews.get(i);
3408 if (v != null) {
3409 boolean attached = true;
3410 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3411 attached = v.isAttachedToWindow();
3412 }
3413 if (attached) v.buildLayer();
3414 }
3415 }
3416 }
3417 mStateAnimation.start();
3418 }
3419 };
3420 toView.bringToFront();
3421 toView.setVisibility(View.VISIBLE);
3422 toView.post(startAnimRunnable);
3423 } else {
3424 toView.setTranslationX(0.0f);
3425 toView.setTranslationY(0.0f);
3426 toView.setScaleX(1.0f);
3427 toView.setScaleY(1.0f);
3428 toView.setVisibility(View.VISIBLE);
3429 toView.bringToFront();
3430
3431 if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) {
3432 // Hide the search bar
3433 if (mSearchDropTargetBar != null) {
3434 mSearchDropTargetBar.hideSearchBar(false);
3435 }
3436 }
3437 dispatchOnLauncherTransitionPrepare(fromView, animated, false);
3438 dispatchOnLauncherTransitionStart(fromView, animated, false);
3439 dispatchOnLauncherTransitionEnd(fromView, animated, false);
3440 dispatchOnLauncherTransitionPrepare(toView, animated, false);
3441 dispatchOnLauncherTransitionStart(toView, animated, false);
3442 dispatchOnLauncherTransitionEnd(toView, animated, false);
3443 }
3444 }
3445
3446 /**
3447 * Zoom the camera back into the workspace, hiding 'fromView'.
3448 * This is the opposite of showAppsCustomizeHelper.
3449 * @param animated If true, the transition will be animated.
3450 */
3451 private void hideAppsCustomizeHelper(Workspace.State toState, final boolean animated,
3452 final boolean springLoaded, final Runnable onCompleteRunnable) {
3453
3454 if (mStateAnimation != null) {
3455 mStateAnimation.setDuration(0);
3456 mStateAnimation.cancel();
3457 mStateAnimation = null;
3458 }
3459
3460 boolean material = Utilities.isLmpOrAbove();
3461 Resources res = getResources();
3462
3463 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
3464 final int fadeOutDuration = res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
3465 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeConcealTime);
3466 final int itemsAlphaStagger =
3467 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
3468
3469 final float scaleFactor = (float)
3470 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
3471 final View fromView = mAppsCustomizeTabHost;
3472 final View toView = mWorkspace;
3473 Animator workspaceAnim = null;
3474 final ArrayList<View> layerViews = new ArrayList<View>();
3475
3476 if (toState == Workspace.State.NORMAL) {
3477 workspaceAnim = mWorkspace.getChangeStateAnimation(
3478 toState, animated, layerViews);
3479 } else if (toState == Workspace.State.SPRING_LOADED ||
3480 toState == Workspace.State.OVERVIEW) {
3481 workspaceAnim = mWorkspace.getChangeStateAnimation(
3482 toState, animated, layerViews);
3483 }
3484
3485 // If for some reason our views aren't initialized, don't animate
3486 boolean initialized = getAllAppsButton() != null;
3487
3488 if (animated && initialized) {
3489 mStateAnimation = LauncherAnimUtils.createAnimatorSet();
3490 if (workspaceAnim != null) {
3491 mStateAnimation.play(workspaceAnim);
3492 }
3493
3494 final AppsCustomizePagedView content = (AppsCustomizePagedView)
3495 fromView.findViewById(R.id.apps_customize_pane_content);
3496
3497 final View page = content.getPageAt(content.getNextPage());
3498
3499 // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases
3500 int count = content.getChildCount();
3501 for (int i = 0; i < count; i++) {
3502 View child = content.getChildAt(i);
3503 if (child != page) {
3504 child.setVisibility(View.INVISIBLE);
3505 }
3506 }
3507 final View revealView = fromView.findViewById(R.id.fake_page);
3508
3509 // hideAppsCustomizeHelper is called in some cases when it is already hidden
3510 // don't perform all these no-op animations. In particularly, this was causing
3511 // the all-apps button to pop in and out.
3512 if (fromView.getVisibility() == View.VISIBLE) {
3513 AppsCustomizePagedView.ContentType contentType = content.getContentType();
3514 final boolean isWidgetTray =
3515 contentType == AppsCustomizePagedView.ContentType.Widgets;
3516
3517 if (isWidgetTray) {
3518 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
3519 } else {
3520 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
3521 }
3522
3523 int width = revealView.getMeasuredWidth();
3524 int height = revealView.getMeasuredHeight();
3525 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
3526
3527 // Hide the real page background, and swap in the fake one
3528 revealView.setVisibility(View.VISIBLE);
3529 content.setPageBackgroundsVisible(false);
3530
3531 final View allAppsButton = getAllAppsButton();
3532 revealView.setTranslationY(0);
3533 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
3534 allAppsButton, null);
3535
3536 float xDrift = 0;
3537 float yDrift = 0;
3538 if (material) {
3539 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
3540 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
3541 } else {
3542 yDrift = 5 * height / 4;
3543 xDrift = 0;
3544 }
3545
3546 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3547 TimeInterpolator decelerateInterpolator = material ?
3548 new LogDecelerateInterpolator(100, 0) :
3549 new LogDecelerateInterpolator(30, 0);
3550
3551 // The vertical motion of the apps panel should be delayed by one frame
3552 // from the conceal animation in order to give the right feel. We correpsondingly
3553 // shorten the duration so that the slide and conceal end at the same time.
3554 ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY",
3555 0, yDrift);
3556 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3557 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3558 panelDriftY.setInterpolator(decelerateInterpolator);
3559 mStateAnimation.play(panelDriftY);
3560
3561 ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX",
3562 0, xDrift);
3563 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3564 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3565 panelDriftX.setInterpolator(decelerateInterpolator);
3566 mStateAnimation.play(panelDriftX);
3567
3568 if (isWidgetTray || !material) {
3569 float finalAlpha = material ? 0.4f : 0f;
3570 revealView.setAlpha(1f);
3571 ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha",
3572 1f, finalAlpha);
3573 panelAlpha.setDuration(revealDuration);
3574 panelAlpha.setInterpolator(material ? decelerateInterpolator :
3575 new AccelerateInterpolator(1.5f));
3576 mStateAnimation.play(panelAlpha);
3577 }
3578
3579 if (page != null) {
3580 page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3581
3582 ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(page, "translationY",
3583 0, yDrift);
3584 page.setTranslationY(0);
3585 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3586 pageDrift.setInterpolator(decelerateInterpolator);
3587 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3588 mStateAnimation.play(pageDrift);
3589
3590 page.setAlpha(1f);
3591 ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(page, "alpha", 1f, 0f);
3592 itemsAlpha.setDuration(100);
3593 itemsAlpha.setInterpolator(decelerateInterpolator);
3594 mStateAnimation.play(itemsAlpha);
3595 }
3596
3597 View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator);
3598 pageIndicators.setAlpha(1f);
3599 ObjectAnimator indicatorsAlpha =
3600 LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f);
3601 indicatorsAlpha.setDuration(revealDuration);
3602 indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f));
3603 mStateAnimation.play(indicatorsAlpha);
3604
3605 width = revealView.getMeasuredWidth();
3606
3607 if (material) {
3608 if (!isWidgetTray) {
3609 allAppsButton.setVisibility(View.INVISIBLE);
3610 }
3611 int allAppsButtonSize = LauncherAppState.getInstance().
3612 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
3613 float finalRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
3614 Animator reveal =
3615 LauncherAnimUtils.createCircularReveal(revealView, width / 2,
3616 height / 2, revealRadius, finalRadius);
3617 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
3618 reveal.setDuration(revealDuration);
3619 reveal.setStartDelay(itemsAlphaStagger);
3620
3621 reveal.addListener(new AnimatorListenerAdapter() {
3622 public void onAnimationEnd(Animator animation) {
3623 revealView.setVisibility(View.INVISIBLE);
3624 if (!isWidgetTray) {
3625 allAppsButton.setVisibility(View.VISIBLE);
3626 }
3627 }
3628 });
3629
3630 mStateAnimation.play(reveal);
3631 }
3632
3633 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
3634 dispatchOnLauncherTransitionPrepare(toView, animated, true);
3635 mAppsCustomizeContent.stopScrolling();
3636 }
3637
3638 mStateAnimation.addListener(new AnimatorListenerAdapter() {
3639 @Override
3640 public void onAnimationEnd(Animator animation) {
3641 fromView.setVisibility(View.GONE);
3642 dispatchOnLauncherTransitionEnd(fromView, animated, true);
3643 dispatchOnLauncherTransitionEnd(toView, animated, true);
3644 if (onCompleteRunnable != null) {
3645 onCompleteRunnable.run();
3646 }
3647
3648 revealView.setLayerType(View.LAYER_TYPE_NONE, null);
3649 if (page != null) {
3650 page.setLayerType(View.LAYER_TYPE_NONE, null);
3651 }
3652 content.setPageBackgroundsVisible(true);
3653 // Unhide side pages
3654 int count = content.getChildCount();
3655 for (int i = 0; i < count; i++) {
3656 View child = content.getChildAt(i);
3657 child.setVisibility(View.VISIBLE);
3658 }
3659
3660 // Reset page transforms
3661 if (page != null) {
3662 page.setTranslationX(0);
3663 page.setTranslationY(0);
3664 page.setAlpha(1);
3665 }
3666 content.setCurrentPage(content.getNextPage());
3667
3668 mAppsCustomizeContent.updateCurrentPageScroll();
3669 }
3670 });
3671
3672 final AnimatorSet stateAnimation = mStateAnimation;
3673 final Runnable startAnimRunnable = new Runnable() {
3674 public void run() {
3675 // Check that mStateAnimation hasn't changed while
3676 // we waited for a layout/draw pass
3677 if (mStateAnimation != stateAnimation)
3678 return;
3679 dispatchOnLauncherTransitionStart(fromView, animated, false);
3680 dispatchOnLauncherTransitionStart(toView, animated, false);
3681
3682 if (Utilities.isLmpOrAbove()) {
3683 for (int i = 0; i < layerViews.size(); i++) {
3684 View v = layerViews.get(i);
3685 if (v != null) {
3686 boolean attached = true;
3687 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3688 attached = v.isAttachedToWindow();
3689 }
3690 if (attached) v.buildLayer();
3691 }
3692 }
3693 }
3694 mStateAnimation.start();
3695 }
3696 };
3697 fromView.post(startAnimRunnable);
3698 } else {
3699 fromView.setVisibility(View.GONE);
3700 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
3701 dispatchOnLauncherTransitionStart(fromView, animated, true);
3702 dispatchOnLauncherTransitionEnd(fromView, animated, true);
3703 dispatchOnLauncherTransitionPrepare(toView, animated, true);
3704 dispatchOnLauncherTransitionStart(toView, animated, true);
3705 dispatchOnLauncherTransitionEnd(toView, animated, true);
3706 }
3707 }
3708
3709 @Override
3710 public void onTrimMemory(int level) {
3711 super.onTrimMemory(level);
3712 if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
3713 mAppsCustomizeTabHost.onTrimMemory();
3714 }
3715 }
3716
3717 protected void showWorkspace(boolean animated) {
3718 showWorkspace(animated, null);
3719 }
3720
3721 protected void showWorkspace() {
3722 showWorkspace(true);
3723 }
3724
3725 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3726 if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) {
3727 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3728 mWorkspace.setVisibility(View.VISIBLE);
3729 hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable);
3730
3731 // Show the search bar (only animate if we were showing the drop target bar in spring
3732 // loaded mode)
3733 if (mSearchDropTargetBar != null) {
3734 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3735 }
3736
3737 // Set focus to the AppsCustomize button
3738 if (mAllAppsButton != null) {
3739 mAllAppsButton.requestFocus();
3740 }
3741 }
3742
3743 // Change the state *after* we've called all the transition code
3744 mState = State.WORKSPACE;
3745
3746 // Resume the auto-advance of widgets
3747 mUserPresent = true;
3748 updateRunning();
3749
3750 // Send an accessibility event to announce the context change
3751 getWindow().getDecorView()
3752 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3753
3754 onWorkspaceShown(animated);
3755 }
3756
3757 void showOverviewMode(boolean animated) {
3758 mWorkspace.setVisibility(View.VISIBLE);
3759 hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null);
3760 mState = State.WORKSPACE;
3761 onWorkspaceShown(animated);
3762 }
3763
3764 public void onWorkspaceShown(boolean animated) {
3765 }
3766
3767 void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
3768 boolean resetPageToZero) {
3769 if (mState != State.WORKSPACE) return;
3770
3771 if (resetPageToZero) {
3772 mAppsCustomizeTabHost.reset();
3773 }
3774 showAppsCustomizeHelper(animated, false, contentType);
3775 mAppsCustomizeTabHost.post(new Runnable() {
3776 @Override
3777 public void run() {
3778 // We post this in-case the all apps view isn't yet constructed.
3779 mAppsCustomizeTabHost.requestFocus();
3780 }
3781 });
3782
3783 // Change the state *after* we've called all the transition code
3784 mState = State.APPS_CUSTOMIZE;
3785
3786 // Pause the auto-advance of widgets until we are out of AllApps
3787 mUserPresent = false;
3788 updateRunning();
3789 closeFolder();
3790
3791 // Send an accessibility event to announce the context change
3792 getWindow().getDecorView()
3793 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3794 }
3795
3796 void enterSpringLoadedDragMode() {
3797 if (isAllAppsVisible()) {
3798 hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null);
3799 mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
3800 }
3801 }
3802
3803 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3804 final Runnable onCompleteRunnable) {
3805 if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
3806
3807 mHandler.postDelayed(new Runnable() {
3808 @Override
3809 public void run() {
3810 if (successfulDrop) {
3811 // Before we show workspace, hide all apps again because
3812 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3813 // clean up our state transition functions
3814 mAppsCustomizeTabHost.setVisibility(View.GONE);
3815 showWorkspace(true, onCompleteRunnable);
3816 } else {
3817 exitSpringLoadedDragMode();
3818 }
3819 }
3820 }, delay);
3821 }
3822
3823 void exitSpringLoadedDragMode() {
3824 if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
3825 final boolean animated = true;
3826 final boolean springLoaded = true;
3827 showAppsCustomizeHelper(animated, springLoaded);
3828 mState = State.APPS_CUSTOMIZE;
3829 }
3830 // Otherwise, we are not in spring loaded mode, so don't do anything.
3831 }
3832
3833 void lockAllApps() {
3834 // TODO
3835 }
3836
3837 void unlockAllApps() {
3838 // TODO
3839 }
3840
3841 /**
3842 * Hides the hotseat area.
3843 */
3844 void hideHotseat(boolean animated) {
3845 if (!LauncherAppState.getInstance().isScreenLarge()) {
3846 if (animated) {
3847 if (mHotseat.getAlpha() != 0f) {
3848 int duration = 0;
3849 if (mSearchDropTargetBar != null) {
3850 duration = mSearchDropTargetBar.getTransitionOutDuration();
3851 }
3852 mHotseat.animate().alpha(0f).setDuration(duration);
3853 }
3854 } else {
3855 mHotseat.setAlpha(0f);
3856 }
3857 }
3858 }
3859
3860 /**
3861 * Add an item from all apps or customize onto the given workspace screen.
3862 * If layout is null, add to the current screen.
3863 */
3864 void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
3865 if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
3866 showOutOfSpaceMessage(isHotseatLayout(layout));
3867 }
3868 }
3869
3870 /** Maps the current orientation to an index for referencing orientation correct global icons */
3871 private int getCurrentOrientationIndexForGlobalIcons() {
3872 // default - 0, landscape - 1
3873 switch (getResources().getConfiguration().orientation) {
3874 case Configuration.ORIENTATION_LANDSCAPE:
3875 return 1;
3876 default:
3877 return 0;
3878 }
3879 }
3880
3881 private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
3882 try {
3883 PackageManager packageManager = getPackageManager();
3884 // Look for the toolbar icon specified in the activity meta-data
3885 Bundle metaData = packageManager.getActivityInfo(
3886 activityName, PackageManager.GET_META_DATA).metaData;
3887 if (metaData != null) {
3888 int iconResId = metaData.getInt(resourceName);
3889 if (iconResId != 0) {
3890 Resources res = packageManager.getResourcesForActivity(activityName);
3891 return res.getDrawable(iconResId);
3892 }
3893 }
3894 } catch (NameNotFoundException e) {
3895 // This can happen if the activity defines an invalid drawable
3896 Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
3897 " not found", e);
3898 } catch (Resources.NotFoundException nfe) {
3899 // This can happen if the activity defines an invalid drawable
3900 Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
3901 nfe);
3902 }
3903 return null;
3904 }
3905
3906 // if successful in getting icon, return it; otherwise, set button to use default drawable
3907 private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
3908 int buttonId, ComponentName activityName, int fallbackDrawableId,
3909 String toolbarResourceName) {
3910 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
3911 Resources r = getResources();
3912 int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
3913 int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
3914
3915 TextView button = (TextView) findViewById(buttonId);
3916 // If we were unable to find the icon via the meta-data, use a generic one
3917 if (toolbarIcon == null) {
3918 toolbarIcon = r.getDrawable(fallbackDrawableId);
3919 toolbarIcon.setBounds(0, 0, w, h);
3920 if (button != null) {
3921 button.setCompoundDrawables(toolbarIcon, null, null, null);
3922 }
3923 return null;
3924 } else {
3925 toolbarIcon.setBounds(0, 0, w, h);
3926 if (button != null) {
3927 button.setCompoundDrawables(toolbarIcon, null, null, null);
3928 }
3929 return toolbarIcon.getConstantState();
3930 }
3931 }
3932
3933 // if successful in getting icon, return it; otherwise, set button to use default drawable
3934 private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
3935 int buttonId, ComponentName activityName, int fallbackDrawableId,
3936 String toolbarResourceName) {
3937 ImageView button = (ImageView) findViewById(buttonId);
3938 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
3939
3940 if (button != null) {
3941 // If we were unable to find the icon via the meta-data, use a
3942 // generic one
3943 if (toolbarIcon == null) {
3944 button.setImageResource(fallbackDrawableId);
3945 } else {
3946 button.setImageDrawable(toolbarIcon);
3947 }
3948 }
3949
3950 return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
3951
3952 }
3953
3954 private void updateTextButtonWithDrawable(int buttonId, Drawable d) {
3955 TextView button = (TextView) findViewById(buttonId);
3956 button.setCompoundDrawables(d, null, null, null);
3957 }
3958
3959 private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
3960 ImageView button = (ImageView) findViewById(buttonId);
3961 button.setImageDrawable(d.newDrawable(getResources()));
3962 }
3963
3964 private void invalidatePressedFocusedStates(View container, View button) {
3965 if (container instanceof HolographicLinearLayout) {
3966 HolographicLinearLayout layout = (HolographicLinearLayout) container;
3967 layout.invalidatePressedFocusedStates();
3968 } else if (button instanceof HolographicImageView) {
3969 HolographicImageView view = (HolographicImageView) button;
3970 view.invalidatePressedFocusedStates();
3971 }
3972 }
3973
3974 public View getQsbBar() {
3975 if (mQsb == null) {
3976 mQsb = mInflater.inflate(R.layout.qsb, mSearchDropTargetBar, false);
3977 mSearchDropTargetBar.addView(mQsb);
3978 }
3979 return mQsb;
3980 }
3981
3982 protected boolean updateGlobalSearchIcon() {
3983 final View searchButtonContainer = findViewById(R.id.search_button_container);
3984 final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
3985 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
3986 final View voiceButton = findViewById(R.id.voice_button);
3987
3988 final SearchManager searchManager =
3989 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
3990 ComponentName activityName = searchManager.getGlobalSearchActivity();
3991 if (activityName != null) {
3992 int coi = getCurrentOrientationIndexForGlobalIcons();
3993 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3994 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
3995 TOOLBAR_SEARCH_ICON_METADATA_NAME);
3996 if (sGlobalSearchIcon[coi] == null) {
3997 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
3998 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
3999 TOOLBAR_ICON_METADATA_NAME);
4000 }
4001
4002 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
4003 searchButton.setVisibility(View.VISIBLE);
4004 invalidatePressedFocusedStates(searchButtonContainer, searchButton);
4005 return true;
4006 } else {
4007 // We disable both search and voice search when there is no global search provider
4008 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
4009 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
4010 if (searchButton != null) searchButton.setVisibility(View.GONE);
4011 if (voiceButton != null) voiceButton.setVisibility(View.GONE);
4012 updateVoiceButtonProxyVisible(false);
4013 return false;
4014 }
4015 }
4016
4017 protected void updateGlobalSearchIcon(Drawable.ConstantState d) {
4018 final View searchButtonContainer = findViewById(R.id.search_button_container);
4019 final View searchButton = (ImageView) findViewById(R.id.search_button);
4020 updateButtonWithDrawable(R.id.search_button, d);
4021 invalidatePressedFocusedStates(searchButtonContainer, searchButton);
4022 }
4023
4024 protected boolean updateVoiceSearchIcon(boolean searchVisible) {
4025 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4026 final View voiceButton = findViewById(R.id.voice_button);
4027
4028 // We only show/update the voice search icon if the search icon is enabled as well
4029 final SearchManager searchManager =
4030 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
4031 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
4032
4033 ComponentName activityName = null;
4034 if (globalSearchActivity != null) {
4035 // Check if the global search activity handles voice search
4036 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
4037 intent.setPackage(globalSearchActivity.getPackageName());
4038 activityName = intent.resolveActivity(getPackageManager());
4039 }
4040
4041 if (activityName == null) {
4042 // Fallback: check if an activity other than the global search activity
4043 // resolves this
4044 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
4045 activityName = intent.resolveActivity(getPackageManager());
4046 }
4047 if (searchVisible && activityName != null) {
4048 int coi = getCurrentOrientationIndexForGlobalIcons();
4049 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4050 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
4051 TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
4052 if (sVoiceSearchIcon[coi] == null) {
4053 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4054 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
4055 TOOLBAR_ICON_METADATA_NAME);
4056 }
4057 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
4058 voiceButton.setVisibility(View.VISIBLE);
4059 updateVoiceButtonProxyVisible(false);
4060 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
4061 return true;
4062 } else {
4063 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
4064 if (voiceButton != null) voiceButton.setVisibility(View.GONE);
4065 updateVoiceButtonProxyVisible(false);
4066 return false;
4067 }
4068 }
4069
4070 protected void updateVoiceSearchIcon(Drawable.ConstantState d) {
4071 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4072 final View voiceButton = findViewById(R.id.voice_button);
4073 updateButtonWithDrawable(R.id.voice_button, d);
4074 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
4075 }
4076
4077 public void updateVoiceButtonProxyVisible(boolean forceDisableVoiceButtonProxy) {
4078 final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
4079 if (voiceButtonProxy != null) {
4080 boolean visible = !forceDisableVoiceButtonProxy &&
4081 mWorkspace.shouldVoiceButtonProxyBeVisible();
4082 voiceButtonProxy.setVisibility(visible ? View.VISIBLE : View.GONE);
4083 voiceButtonProxy.bringToFront();
4084 }
4085 }
4086
4087 /**
4088 * This is an overrid eot disable the voice button proxy. If disabled is true, then the voice button🔵
4089 * will be hidden regardless of what shouldVoiceButtonProxyBeVisible() returns.
4090 */
4091 public void disableVoiceButtonProxy(boolean disabled) {
4092 updateVoiceButtonProxyVisible(disabled);
4093 }
4094
4095 @Override
4096 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
4097 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
4098 final List<CharSequence> text = event.getText();
4099 text.clear();
4100 // Populate event with a fake title based on the current state.
4101 if (mState == State.APPS_CUSTOMIZE) {
4102 text.add(mAppsCustomizeTabHost.getContentTag());
4103 } else {
4104 text.add(getString(R.string.all_apps_home_button_label));
4105 }
4106 return result;
4107 }
4108
4109 /**
4110 * Receives notifications when system dialogs are to be closed.
4111 */
4112 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
4113 @Override
4114 public void onReceive(Context context, Intent intent) {
4115 closeSystemDialogs();
4116 }
4117 }
4118
4119 /**
4120 * Receives notifications whenever the appwidgets are reset.
4121 */
4122 private class AppWidgetResetObserver extends ContentObserver {
4123 public AppWidgetResetObserver() {
4124 super(new Handler());
4125 }
4126
4127 @Override
4128 public void onChange(boolean selfChange) {
4129 onAppWidgetReset();
4130 }
4131 }
4132
4133 /**
4134 * If the activity is currently paused, signal that we need to run the passed Runnable
4135 * in onResume.
4136 *
4137 * This needs to be called from incoming places where resources might have been loaded
4138 * while we are paused. That is becaues the Configuration might be wrong
4139 * when we're not running, and if it comes back to what it was when we
4140 * were paused, we are not restarted.
4141 *
4142 * Implementation of the method from LauncherModel.Callbacks.
4143 *
4144 * @return true if we are currently paused. The caller might be able to
4145 * skip some work in that case since we will come back again.
4146 */
4147 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
4148 if (mPaused) {
4149 Log.i(TAG, "Deferring update until onResume");
4150 if (deletePreviousRunnables) {
4151 while (mBindOnResumeCallbacks.remove(run)) {
4152 }
4153 }
4154 mBindOnResumeCallbacks.add(run);
4155 return true;
4156 } else {
4157 return false;
4158 }
4159 }
4160
4161 private boolean waitUntilResume(Runnable run) {
4162 return waitUntilResume(run, false);
4163 }
4164
4165 public void addOnResumeCallback(Runnable run) {
4166 mOnResumeCallbacks.add(run);
4167 }
4168
4169 /**
4170 * If the activity is currently paused, signal that we need to re-run the loader
4171 * in onResume.
4172 *
4173 * This needs to be called from incoming places where resources might have been loaded
4174 * while we are paused. That is becaues the Configuration might be wrong
4175 * when we're not running, and if it comes back to what it was when we
4176 * were paused, we are not restarted.
4177 *
4178 * Implementation of the method from LauncherModel.Callbacks.
4179 *
4180 * @return true if we are currently paused. The caller might be able to
4181 * skip some work in that case since we will come back again.
4182 */
4183 public boolean setLoadOnResume() {
4184 if (mPaused) {
4185 Log.i(TAG, "setLoadOnResume");
4186 mOnResumeNeedsLoad = true;
4187 return true;
4188 } else {
4189 return false;
4190 }
4191 }
4192
4193 /**
4194 * Implementation of the method from LauncherModel.Callbacks.
4195 */
4196 public int getCurrentWorkspaceScreen() {
4197 if (mWorkspace != null) {
4198 return mWorkspace.getCurrentPage();
4199 } else {
4200 return SCREEN_COUNT / 2;
4201 }
4202 }
4203
4204 /**
4205 * Refreshes the shortcuts shown on the workspace.
4206 *
4207 * Implementation of the method from LauncherModel.Callbacks.
4208 */
4209 public void startBinding() {
4210 setWorkspaceLoading(true);
4211
4212 // If we're starting binding all over again, clear any bind calls we'd postponed in
4213 // the past (see waitUntilResume) -- we don't need them since we're starting binding
4214 // from scratch again
4215 mBindOnResumeCallbacks.clear();
4216
4217 // Clear the workspace because it's going to be rebound
4218 mWorkspace.clearDropTargets();
4219 mWorkspace.removeAllWorkspaceScreens();
4220
4221 mWidgetsToAdvance.clear();
4222 if (mHotseat != null) {
4223 mHotseat.resetLayout();
4224 }
4225 }
4226
4227 @Override
4228 public void bindScreens(ArrayList<Long> orderedScreenIds) {
4229 bindAddScreens(orderedScreenIds);
4230
4231 // If there are no screens, we need to have an empty screen
4232 if (orderedScreenIds.size() == 0) {
4233 mWorkspace.addExtraEmptyScreen();
4234 }
4235
4236 // Create the custom content page (this call updates mDefaultScreen which calls
4237 // setCurrentPage() so ensure that all pages are added before calling this).
4238 if (hasCustomContentToLeft()) {
4239 mWorkspace.createCustomContentContainer();
4240 populateCustomContentContainer();
4241 }
4242 }
4243
4244 @Override
4245 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
4246 // Log to disk
4247 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
4248 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
4249 TextUtils.join(", ", orderedScreenIds), true);
4250 int count = orderedScreenIds.size();
4251 for (int i = 0; i < count; i++) {
4252 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
4253 }
4254 }
4255
4256 private boolean shouldShowWeightWatcher() {
4257 String spKey = LauncherAppState.getSharedPreferencesKey();
4258 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4259 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
4260
4261 return show;
4262 }
4263
4264 private void toggleShowWeightWatcher() {
4265 String spKey = LauncherAppState.getSharedPreferencesKey();
4266 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4267 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
4268
4269 show = !show;
4270
4271 SharedPreferences.Editor editor = sp.edit();
4272 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
4273 editor.commit();
4274
4275 if (mWeightWatcher != null) {
4276 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
4277 }
4278 }
4279
4280 public void bindAppsAdded(final ArrayList<Long> newScreens,
4281 final ArrayList<ItemInfo> addNotAnimated,
4282 final ArrayList<ItemInfo> addAnimated,
4283 final ArrayList<AppInfo> addedApps) {
4284 Runnable r = new Runnable() {
4285 public void run() {
4286 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
4287 }
4288 };
4289 if (waitUntilResume(r)) {
4290 return;
4291 }
4292
4293 // Add the new screens
4294 if (newScreens != null) {
4295 bindAddScreens(newScreens);
4296 }
4297
4298 // We add the items without animation on non-visible pages, and with
4299 // animations on the new page (which we will try and snap to).
4300 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
4301 bindItems(addNotAnimated, 0,
4302 addNotAnimated.size(), false);
4303 }
4304 if (addAnimated != null && !addAnimated.isEmpty()) {
4305 bindItems(addAnimated, 0,
4306 addAnimated.size(), true);
4307 }
4308
4309 // Remove the extra empty screen
4310 mWorkspace.removeExtraEmptyScreen(false, false);
4311
4312 if (!LauncherAppState.isDisableAllApps() &&
4313 addedApps != null && mAppsCustomizeContent != null) {
4314 mAppsCustomizeContent.addApps(addedApps);
4315 }
4316 }
4317
4318 /**
4319 * Bind the items start-end from the list.
4320 *
4321 * Implementation of the method from LauncherModel.Callbacks.
4322 */
4323 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
4324 final boolean forceAnimateIcons) {
4325 Runnable r = new Runnable() {
4326 public void run() {
4327 bindItems(shortcuts, start, end, forceAnimateIcons);
4328 }
4329 };
4330 if (waitUntilResume(r)) {
4331 return;
4332 }
4333
4334 // Get the list of added shortcuts and intersect them with the set of shortcuts here
4335 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
4336 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
4337 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
4338 Workspace workspace = mWorkspace;
4339 long newShortcutsScreenId = -1;
4340 for (int i = start; i < end; i++) {
4341 final ItemInfo item = shortcuts.get(i);
4342
4343 // Short circuit if we are loading dock items for a configuration which has no dock
4344 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
4345 mHotseat == null) {
4346 continue;
4347 }
4348
4349 switch (item.itemType) {
4350 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
4351 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
4352 ShortcutInfo info = (ShortcutInfo) item;
4353 View shortcut = createShortcut(info);
4354
4355 /*
4356 * TODO: FIX collision case
4357 */
4358 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
4359 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
4360 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
4361 View v = cl.getChildAt(item.cellX, item.cellY);
4362 Object tag = v.getTag();
4363 String desc = "Collision while binding workspace item: " + item
4364 + ". Collides with " + tag;
4365 if (LauncherAppState.isDogfoodBuild()) {
4366 throw (new RuntimeException(desc));
4367 } else {
4368 Log.d(TAG, desc);
4369 }
4370 }
4371 }
4372
4373 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
4374 item.cellY, 1, 1);
4375 if (animateIcons) {
4376 // Animate all the applications up now
4377 shortcut.setAlpha(0f);
4378 shortcut.setScaleX(0f);
4379 shortcut.setScaleY(0f);
4380 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
4381 newShortcutsScreenId = item.screenId;
4382 }
4383 break;
4384 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
4385 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
4386 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
4387 (FolderInfo) item, mIconCache);
4388 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
4389 item.cellY, 1, 1);
4390 break;
4391 default:
4392 throw new RuntimeException("Invalid Item Type");
4393 }
4394 }
4395
4396 if (animateIcons) {
4397 // Animate to the correct page
4398 if (newShortcutsScreenId > -1) {
4399 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
4400 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
4401 final Runnable startBounceAnimRunnable = new Runnable() {
4402 public void run() {
4403 anim.playTogether(bounceAnims);
4404 anim.start();
4405 }
4406 };
4407 if (newShortcutsScreenId != currentScreenId) {
4408 // We post the animation slightly delayed to prevent slowdowns
4409 // when we are loading right after we return to launcher.
4410 mWorkspace.postDelayed(new Runnable() {
4411 public void run() {
4412 if (mWorkspace != null) {
4413 mWorkspace.snapToPage(newScreenIndex);
4414 mWorkspace.postDelayed(startBounceAnimRunnable,
4415 NEW_APPS_ANIMATION_DELAY);
4416 }
4417 }
4418 }, NEW_APPS_PAGE_MOVE_DELAY);
4419 } else {
4420 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
4421 }
4422 }
4423 }
4424 workspace.requestLayout();
4425 }
4426
4427 /**
4428 * Implementation of the method from LauncherModel.Callbacks.
4429 */
4430 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
4431 Runnable r = new Runnable() {
4432 public void run() {
4433 bindFolders(folders);
4434 }
4435 };
4436 if (waitUntilResume(r)) {
4437 return;
4438 }
4439 sFolders.clear();
4440 sFolders.putAll(folders);
4441 }
4442
4443 /**
4444 * Add the views for a widget to the workspace.
4445 *
4446 * Implementation of the method from LauncherModel.Callbacks.
4447 */
4448 public void bindAppWidget(final LauncherAppWidgetInfo item) {
4449 Runnable r = new Runnable() {
4450 public void run() {
4451 bindAppWidget(item);
4452 }
4453 };
4454 if (waitUntilResume(r)) {
4455 return;
4456 }
4457
4458 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
4459 if (DEBUG_WIDGETS) {
4460 Log.d(TAG, "bindAppWidget: " + item);
4461 }
4462 final Workspace workspace = mWorkspace;
4463
4464 AppWidgetProviderInfo appWidgetInfo;
4465 if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) &&
4466 ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
4467
4468 appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);
4469 if (appWidgetInfo == null) {
4470 if (DEBUG_WIDGETS) {
4471 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4472 + " belongs to component " + item.providerName
4473 + ", as the povider is null");
4474 }
4475 LauncherModel.deleteItemFromDatabase(this, item);
4476 return;
4477 }
4478 // Note: This assumes that the id remap broadcast is received before this step.
4479 // If that is not the case, the id remap will be ignored and user may see the
4480 // click to setup view.
4481 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);
4482 pendingInfo.spanX = item.spanX;
4483 pendingInfo.spanY = item.spanY;
4484 pendingInfo.minSpanX = item.minSpanX;
4485 pendingInfo.minSpanY = item.minSpanY;
4486 Bundle options =
4487 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
4488
4489 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
4490 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
4491 newWidgetId, appWidgetInfo, options);
4492
4493 // TODO consider showing a permission dialog when the widget is clicked.
4494 if (!success) {
4495 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
4496 if (DEBUG_WIDGETS) {
4497 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4498 + " belongs to component " + item.providerName
4499 + ", as the launcher is unable to bing a new widget id");
4500 }
4501 LauncherModel.deleteItemFromDatabase(this, item);
4502 return;
4503 }
4504
4505 item.appWidgetId = newWidgetId;
4506
4507 // If the widget has a configure activity, it is still needs to set it up, otherwise
4508 // the widget is ready to go.
4509 item.restoreStatus = (appWidgetInfo.configure == null)
4510 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4511 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4512
4513 LauncherModel.updateItemInDatabase(this, item);
4514 }
4515
4516 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4517 final int appWidgetId = item.appWidgetId;
4518 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
4519 if (DEBUG_WIDGETS) {
4520 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidget🔵
4521 }
4522
4523 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4524 } else {
4525 appWidgetInfo = null;
4526 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item);
4527 view.updateIcon(mIconCache);
4528 item.hostView = view;
4529 item.hostView.updateAppWidget(null);
4530 item.hostView.setOnClickListener(this);
4531 }
4532
4533 item.hostView.setTag(item);
4534 item.onBindAppWidget(this);
4535
4536 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4537 item.cellY, item.spanX, item.spanY, false);
4538 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4539
4540 workspace.requestLayout();
4541
4542 if (DEBUG_WIDGETS) {
4543 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4544 + (SystemClock.uptimeMillis()-start) + "ms");
4545 }
4546 }
4547
4548 /**
4549 * Restores a pending widget.
4550 *
4551 * @param appWidgetId The app widget id
4552 * @param cellInfo The position on screen where to create the widget.
4553 */
4554 private void completeRestoreAppWidget(final int appWidgetId) {
4555 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4556 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4557 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4558 return;
4559 }
4560
4561 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4562 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4563
4564 mWorkspace.reinflateWidgetsIfNecessary();
4565 LauncherModel.updateItemInDatabase(this, info);
4566 }
4567
4568 public void onPageBoundSynchronously(int page) {
4569 mSynchronouslyBoundPages.add(page);
4570 }
4571
4572 /**
4573 * Callback saying that there aren't any more items to bind.
4574 *
4575 * Implementation of the method from LauncherModel.Callbacks.
4576 */
4577 public void finishBindingItems(final boolean upgradePath) {
4578 Runnable r = new Runnable() {
4579 public void run() {
4580 finishBindingItems(upgradePath);
4581 }
4582 };
4583 if (waitUntilResume(r)) {
4584 return;
4585 }
4586 if (mSavedState != null) {
4587 if (!mWorkspace.hasFocus()) {
4588 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4589 }
4590 mSavedState = null;
4591 }
4592
4593 mWorkspace.restoreInstanceStateForRemainingPages();
4594
4595 setWorkspaceLoading(false);
4596 sendLoadingCompleteBroadcastIfNecessary();
4597
4598 // If we received the result of any pending adds while the loader was running (e.g. the
4599 // widget configuration forced an orientation change), process them now.
4600 if (sPendingAddItem != null) {
4601 final long screenId = completeAdd(sPendingAddItem);
4602
4603 // TODO: this moves the user to the page where the pending item was added. Ideally,
4604 // the screen would be guaranteed to exist after bind, and the page would be set through
4605 // the workspace restore process.
4606 mWorkspace.post(new Runnable() {
4607 @Override
4608 public void run() {
4609 mWorkspace.snapToScreenId(screenId);
4610 }
4611 });
4612 sPendingAddItem = null;
4613 }
4614
4615 if (upgradePath) {
4616 mWorkspace.getUniqueComponents(true, null);
4617 mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
4618 }
4619 PackageInstallerCompat.getInstance(this).onFinishBind();
4620 mModel.recheckRestoredItems(this);
4621 }
4622
4623 private void sendLoadingCompleteBroadcastIfNecessary() {
4624 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4625 String permission =
4626 getResources().getString(R.string.receive_first_load_broadcast_permission);
4627 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4628 sendBroadcast(intent, permission);
4629 SharedPreferences.Editor editor = mSharedPrefs.edit();
4630 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4631 editor.apply();
4632 }
4633 }
4634
4635 public boolean isAllAppsButtonRank(int rank) {
4636 if (mHotseat != null) {
4637 return mHotseat.isAllAppsButtonRank(rank);
4638 }
4639 return false;
4640 }
4641
4642 private boolean canRunNewAppsAnimation() {
4643 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4644 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4645 }
4646
4647 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4648 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4649 PropertyValuesHolder.ofFloat("alpha", 1f),
4650 PropertyValuesHolder.ofFloat("scaleX", 1f),
4651 PropertyValuesHolder.ofFloat("scaleY", 1f));
4652 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4653 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4654 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4655 return bounceAnim;
4656 }
4657
4658 public boolean useVerticalBarLayout() {
4659 return LauncherAppState.getInstance().getDynamicGrid().
4660 getDeviceProfile().isVerticalBarLayout();
4661 }
4662
4663 protected Rect getSearchBarBounds() {
4664 return LauncherAppState.getInstance().getDynamicGrid().
4665 getDeviceProfile().getSearchBarBounds();
4666 }
4667
4668 @Override
4669 public void bindSearchablesChanged() {
4670 boolean searchVisible = updateGlobalSearchIcon();
4671 boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
4672 if (mSearchDropTargetBar != null) {
4673 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
4674 }
4675 }
4676
4677 /**
4678 * Add the icons for all apps.
4679 *
4680 * Implementation of the method from LauncherModel.Callbacks.
4681 */
4682 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4683 if (LauncherAppState.isDisableAllApps()) {
4684 if (mIntentsOnWorkspaceFromUpgradePath != null) {
4685 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
4686 getHotseat().addAllAppsFolder(mIconCache, apps,
4687 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
4688 }
4689 mIntentsOnWorkspaceFromUpgradePath = null;
4690 }
4691 if (mAppsCustomizeContent != null) {
4692 mAppsCustomizeContent.onPackagesUpdated(
4693 LauncherModel.getSortedWidgetsAndShortcuts(this));
4694 }
4695 } else {
4696 if (mAppsCustomizeContent != null) {
4697 mAppsCustomizeContent.setApps(apps);
4698 mAppsCustomizeContent.onPackagesUpdated(
4699 LauncherModel.getSortedWidgetsAndShortcuts(this));
4700 }
4701 }
4702 }
4703
4704 /**
4705 * A package was updated.
4706 *
4707 * Implementation of the method from LauncherModel.Callbacks.
4708 */
4709 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4710 Runnable r = new Runnable() {
4711 public void run() {
4712 bindAppsUpdated(apps);
4713 }
4714 };
4715 if (waitUntilResume(r)) {
4716 return;
4717 }
4718
4719 if (mWorkspace != null) {
4720 mWorkspace.updateShortcutsAndWidgets(apps);
4721 }
4722
4723 if (!LauncherAppState.isDisableAllApps() &&
4724 mAppsCustomizeContent != null) {
4725 mAppsCustomizeContent.updateApps(apps);
4726 }
4727 }
4728
4729 /**
4730 * Some shortcuts were updated in the background.
4731 *
4732 * Implementation of the method from LauncherModel.Callbacks.
4733 */
4734 public void bindShortcutsUpdated(final ArrayList<ShortcutInfo> shortcuts) {
4735 Runnable r = new Runnable() {
4736 public void run() {
4737 bindShortcutsUpdated(shortcuts);
4738 }
4739 };
4740 if (waitUntilResume(r)) {
4741 return;
4742 }
4743
4744 if (mWorkspace != null) {
4745 mWorkspace.updateShortcuts(shortcuts);
4746 }
4747 }
4748
4749 /**
4750 * Packages were restored
4751 */
4752 public void bindAppsRestored(final ArrayList<AppInfo> apps) {
4753 Runnable r = new Runnable() {
4754 public void run() {
4755 bindAppsRestored(apps);
4756 }
4757 };
4758 if (waitUntilResume(r)) {
4759 return;
4760 }
4761
4762 if (mWorkspace != null) {
4763 mWorkspace.updateShortcutsAndWidgets(apps);
4764 }
4765 }
4766
4767 /**
4768 * Update the state of a package, typically related to install state.
4769 *
4770 * Implementation of the method from LauncherModel.Callbacks.
4771 */
4772 @Override
4773 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4774 if (mWorkspace != null) {
4775 mWorkspace.updatePackageState(installInfo);
4776 }
4777 }
4778
4779 /**
4780 * Update the label and icon of all the icons in a package
4781 *
4782 * Implementation of the method from LauncherModel.Callbacks.
4783 */
4784 @Override
4785 public void updatePackageBadge(String packageName) {
4786 if (mWorkspace != null) {
4787 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4788 }
4789 }
4790
4791 /**
4792 * A package was uninstalled. We take both the super set of packageNames
4793 * in addition to specific applications to remove, the reason being that
4794 * this can be called when a package is updated as well. In that scenario,
4795 * we only remove specific components from the workspace, where as
4796 * package-removal should clear all items by package name.
4797 *
4798 * @param reason if non-zero, the icons are not permanently removed, rather marked as disabled.
4799 * Implementation of the method from LauncherModel.Callbacks.
4800 */
4801 @Override
4802 public void bindComponentsRemoved(final ArrayList<String> packageNames,
4803 final ArrayList<AppInfo> appInfos, final UserHandleCompat user, final int reason) {
4804 Runnable r = new Runnable() {
4805 public void run() {
4806 bindComponentsRemoved(packageNames, appInfos, user, reason);
4807 }
4808 };
4809 if (waitUntilResume(r)) {
4810 return;
4811 }
4812
4813 if (reason == 0) {
4814 if (!packageNames.isEmpty()) {
4815 mWorkspace.removeItemsByPackageName(packageNames, user);
4816 }
4817 if (!appInfos.isEmpty()) {
4818 mWorkspace.removeItemsByApplicationInfo(appInfos, user);
4819 }
4820 } else {
4821 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4822 }
4823
4824 // Notify the drag controller
4825 mDragController.onAppsRemoved(packageNames, appInfos);
4826
4827 // Update AllApps
4828 if (!LauncherAppState.isDisableAllApps() &&
4829 mAppsCustomizeContent != null) {
4830 mAppsCustomizeContent.removeApps(appInfos);
4831 }
4832 }
4833
4834 /**
4835 * A number of packages were updated.
4836 */
4837 private ArrayList<Object> mWidgetsAndShortcuts;
4838 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4839 public void run() {
4840 bindPackagesUpdated(mWidgetsAndShortcuts);
4841 mWidgetsAndShortcuts = null;
4842 }
4843 };
4844 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4845 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4846 mWidgetsAndShortcuts = widgetsAndShortcuts;
4847 return;
4848 }
4849
4850 // Update the widgets pane
4851 if (mAppsCustomizeContent != null) {
4852 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
4853 }
4854 }
4855
4856 private int mapConfigurationOriActivityInfoOri(int configOri) {
4857 final Display d = getWindowManager().getDefaultDisplay();
4858 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4859 switch (d.getRotation()) {
4860 case Surface.ROTATION_0:
4861 case Surface.ROTATION_180:
4862 // We are currently in the same basic orientation as the natural orientation
4863 naturalOri = configOri;
4864 break;
4865 case Surface.ROTATION_90:
4866 case Surface.ROTATION_270:
4867 // We are currently in the other basic orientation to the natural orientation
4868 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4869 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4870 break;
4871 }
4872
4873 int[] oriMap = {
4874 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4875 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4876 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4877 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4878 };
4879 // Since the map starts at portrait, we need to offset if this device's natural orientation
4880 // is landscape.
4881 int indexOffset = 0;
4882 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4883 indexOffset = 1;
4884 }
4885 return oriMap[(d.getRotation() + indexOffset) % 4];
4886 }
4887
4888 public boolean isRotationEnabled() {
4889 boolean enableRotation = sForceEnableRotation ||
4890 getResources().getBoolean(R.bool.allow_rotation);
4891 return enableRotation;
4892 }
4893 public void lockScreenOrientation() {
4894 if (isRotationEnabled()) {
4895 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4896 .getConfiguration().orientation));
4897 }
4898 }
4899 public void unlockScreenOrientation(boolean immediate) {
4900 if (isRotationEnabled()) {
4901 if (immediate) {
4902 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4903 } else {
4904 mHandler.postDelayed(new Runnable() {
4905 public void run() {
4906 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4907 }
4908 }, mRestoreScreenOrientationDelay);
4909 }
4910 }
4911 }
4912
4913 /**
4914 * Called when the SearchBar hint should be changed.
4915 *
4916 * @param hint the hint to be displayed in the search bar.
4917 */
4918 protected void onSearchBarHintChanged(String hint) {
4919
4920 }
4921
4922 protected boolean isLauncherPreinstalled() {
4923 PackageManager pm = getPackageManager();
4924 try {
4925 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
4926 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
4927 return true;
4928 } else {
4929 return false;
4930 }
4931 } catch (NameNotFoundException e) {
4932 e.printStackTrace();
4933 return false;
4934 }
4935 }
4936
4937 /**
4938 * This method indicates whether or not we should suggest default wallpaper dimensions
4939 * when our wallpaper cropper was not yet used to set a wallpaper.
4940 */
4941 protected boolean overrideWallpaperDimensions() {
4942 return true;
4943 }
4944
4945 /**
4946 * To be overridden by subclasses to indicate that there is an activity to launch
4947 * before showing the standard launcher experience.
4948 */
4949 protected boolean hasFirstRunActivity() {
4950 return false;
4951 }
4952
4953 /**
4954 * To be overridden by subclasses to launch any first run activity
4955 */
4956 protected Intent getFirstRunActivity() {
4957 return null;
4958 }
4959
4960 private boolean shouldRunFirstRunActivity() {
4961 return !ActivityManager.isRunningInTestHarness() &&
4962 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4963 }
4964
4965 protected boolean hasRunFirstRunActivity() {
4966 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
4967 }
4968
4969 public boolean showFirstRunActivity() {
4970 if (shouldRunFirstRunActivity() &&
4971 hasFirstRunActivity()) {
4972 Intent firstRunIntent = getFirstRunActivity();
4973 if (firstRunIntent != null) {
4974 startActivity(firstRunIntent);
4975 markFirstRunActivityShown();
4976 return true;
4977 }
4978 }
4979 return false;
4980 }
4981
4982 private void markFirstRunActivityShown() {
4983 SharedPreferences.Editor editor = mSharedPrefs.edit();
4984 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
4985 editor.apply();
4986 }
4987
4988 /**
4989 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
4990 * screen that must be displayed and dismissed.
4991 */
4992 protected boolean hasDismissableIntroScreen() {
4993 return false;
4994 }
4995
4996 /**
4997 * Full screen intro screen to be shown and dismissed before the launcher can be used.
4998 */
4999 protected View getIntroScreen() {
5000 return null;
5001 }
5002
5003 /**
5004 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
5005 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
5006 */
5007 private boolean shouldShowIntroScreen() {
5008 return hasDismissableIntroScreen() &&
5009 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
5010 }
5011
5012 protected void showIntroScreen() {
5013 View introScreen = getIntroScreen();
5014 changeWallpaperVisiblity(false);
5015 if (introScreen != null) {
5016 mDragLayer.showOverlayView(introScreen);
5017 }
5018 }
5019
5020 public void dismissIntroScreen() {
5021 markIntroScreenDismissed();
5022 if (showFirstRunActivity()) {
5023 // We delay hiding the intro view until the first run activity is showing. This
5024 // avoids a blip.
5025 mWorkspace.postDelayed(new Runnable() {
5026 @Override
5027 public void run() {
5028 mDragLayer.dismissOverlayView();
5029 showFirstRunClings();
5030 }
5031 }, ACTIVITY_START_DELAY);
5032 } else {
5033 mDragLayer.dismissOverlayView();
5034 showFirstRunClings();
5035 }
5036 changeWallpaperVisiblity(true);
5037 }
5038
5039 private void markIntroScreenDismissed() {
5040 SharedPreferences.Editor editor = mSharedPrefs.edit();
5041 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
5042 editor.apply();
5043 }
5044
5045 private void showFirstRunClings() {
5046 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
5047 // on the device, then we always show the first run cling experience (or if there is no
5048 // launcher2). Otherwise, we prompt the user upon started for migration
5049 LauncherClings launcherClings = new LauncherClings(this);
5050 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
5051 if (mModel.canMigrateFromOldLauncherDb(this)) {
5052 launcherClings.showMigrationCling();
5053 } else {
5054 launcherClings.showLongPressCling(true);
5055 }
5056 }
5057 }
5058
5059 void showWorkspaceSearchAndHotseat() {
5060 if (mWorkspace != null) mWorkspace.setAlpha(1f);
5061 if (mHotseat != null) mHotseat.setAlpha(1f);
5062 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
5063 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
5064 }
5065
5066 void hideWorkspaceSearchAndHotseat() {
5067 if (mWorkspace != null) mWorkspace.setAlpha(0f);
5068 if (mHotseat != null) mHotseat.setAlpha(0f);
5069 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
5070 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
5071 }
5072
5073 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
5074 // Called from search suggestion, not supported in other profiles.
5075 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
5076 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
5077 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
5078 myUser);
5079 if (activityInfo == null) {
5080 return null;
5081 }
5082 return new AppInfo(this, activityInfo, myUser, mIconCache, null);
5083 }
5084
5085 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5086 Bitmap icon) {
5087 // Called from search suggestion, not supported in other profiles.
5088 return createShortcutDragInfo(shortcutIntent, caption, icon,
5089 UserHandleCompat.myUserHandle());
5090 }
5091
5092 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5093 Bitmap icon, UserHandleCompat user) {
5094 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
5095 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
5096 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
5097 }
5098
5099 protected void moveWorkspaceToDefaultScreen() {
5100 mWorkspace.moveToDefaultScreen(false);
5101 }
5102
5103 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
5104 dragView.setTag(dragInfo);
5105 mWorkspace.onExternalDragStartedWithItem(dragView);
5106 mWorkspace.beginExternalDragShared(dragView, source);
5107 }
5108
5109 @Override
5110 public void onPageSwitch(View newPage, int newPageIndex) {
5111 }
5112
5113 /**
5114 * Prints out out state for debugging.
5115 */
5116 public void dumpState() {
5117 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
5118 Log.d(TAG, "mSavedState=" + mSavedState);
5119 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
5120 Log.d(TAG, "mRestoring=" + mRestoring);
5121 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
5122 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
5123 Log.d(TAG, "sFolders.size=" + sFolders.size());
5124 mModel.dumpState();
5125
5126 if (mAppsCustomizeContent != null) {
5127 mAppsCustomizeContent.dumpState();
5128 }
5129 Log.d(TAG, "END launcher3 dump state");
5130 }
5131
5132 @Override
5133 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
5134 super.dump(prefix, fd, writer, args);
5135 synchronized (sDumpLogs) {
5136 writer.println(" ");
5137 writer.println("Debug logs: ");
5138 for (int i = 0; i < sDumpLogs.size(); i++) {
5139 writer.println(" " + sDumpLogs.get(i));
5140 }
5141 }
5142 }
5143
5144 public static void dumpDebugLogsToConsole() {
5145 if (DEBUG_DUMP_LOG) {
5146 synchronized (sDumpLogs) {
5147 Log.d(TAG, "");
5148 Log.d(TAG, "*********************");
5149 Log.d(TAG, "Launcher debug logs: ");
5150 for (int i = 0; i < sDumpLogs.size(); i++) {
5151 Log.d(TAG, " " + sDumpLogs.get(i));
5152 }
5153 Log.d(TAG, "*********************");
5154 Log.d(TAG, "");
5155 }
5156 }
5157 }
5158
5159 public static void addDumpLog(String tag, String log, boolean debugLog) {
5160 addDumpLog(tag, log, null, debugLog);
5161 }
5162
5163 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
5164 if (debugLog) {
5165 if (e != null) {
5166 Log.d(tag, log, e);
5167 } else {
5168 Log.d(tag, log);
5169 }
5170 }
5171 if (DEBUG_DUMP_LOG) {
5172 sDateStamp.setTime(System.currentTimeMillis());
5173 synchronized (sDumpLogs) {
5174 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
5175 + (e == null ? "" : (", Exception: " + e)));
5176 }
5177 }
5178 }
5179
5180 public void dumpLogsToLocalData() {
5181 if (DEBUG_DUMP_LOG) {
5182 new AsyncTask<Void, Void, Void>() {
5183 public Void doInBackground(Void ... args) {
5184 boolean success = false;
5185 sDateStamp.setTime(sRunStart);
5186 String FILENAME = sDateStamp.getMonth() + "-"
5187 + sDateStamp.getDay() + "_"
5188 + sDateStamp.getHours() + "-"
5189 + sDateStamp.getMinutes() + "_"
5190 + sDateStamp.getSeconds() + ".txt";
5191
5192 FileOutputStream fos = null;
5193 File outFile = null;
5194 try {
5195 outFile = new File(getFilesDir(), FILENAME);
5196 outFile.createNewFile();
5197 fos = new FileOutputStream(outFile);
5198 } catch (Exception e) {
5199 e.printStackTrace();
5200 }
5201 if (fos != null) {
5202 PrintWriter writer = new PrintWriter(fos);
5203
5204 writer.println(" ");
5205 writer.println("Debug logs: ");
5206 synchronized (sDumpLogs) {
5207 for (int i = 0; i < sDumpLogs.size(); i++) {
5208 writer.println(" " + sDumpLogs.get(i));
5209 }
5210 }
5211 writer.close();
5212 }
5213 try {
5214 if (fos != null) {
5215 fos.close();
5216 success = true;
5217 }
5218 } catch (IOException e) {
5219 e.printStackTrace();
5220 }
5221 return null;
5222 }
5223 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
5224 }
5225 }
5226 }
5227
5228 interface LauncherTransitionable {
5229 View getContent();
5230 void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
5231 void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
5232 void onLauncherTransitionStep(Launcher l, float t);
5233 void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
5234 }
5235
5236 interface DebugIntents {
5237 static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
5238 static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
5239 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.TimeInterpolator;
24 import android.animation.ValueAnimator;
25 import android.annotation.TargetApi;
26 import android.app.Activity;
27 import android.app.ActivityManager;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.SearchManager;
31 import android.appwidget.AppWidgetHostView;
32 import android.appwidget.AppWidgetManager;
33 import android.appwidget.AppWidgetProviderInfo;
34 import android.content.ActivityNotFoundException;
35 import android.content.BroadcastReceiver;
36 import android.content.ComponentCallbacks2;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.DialogInterface;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.SharedPreferences;
44 import android.content.pm.ActivityInfo;
45 import android.content.pm.ApplicationInfo;
46 import android.content.pm.PackageManager.NameNotFoundException;
47 import android.content.pm.PackageManager;
48 import android.content.res.Configuration;
49 import android.content.res.Resources;
50 import android.database.ContentObserver;
51 import android.graphics.Bitmap;
52 import android.graphics.Canvas;
53 import android.graphics.Color;
54 import android.graphics.Point;
55 import android.graphics.PorterDuff;
56 import android.graphics.Rect;
57 import android.graphics.drawable.Drawable;
58 import android.net.Uri;
59 import android.os.AsyncTask;
60 import android.os.Build;
61 import android.os.Bundle;
62 import android.os.Environment;
63 import android.os.Handler;
64 import android.os.Message;
65 import android.os.StrictMode;
66 import android.os.SystemClock;
67 import android.speech.RecognizerIntent;
68 import android.text.Selection;
69 import android.text.SpannableStringBuilder;
70 import android.text.TextUtils;
71 import android.text.method.TextKeyListener;
72 import android.util.DisplayMetrics;
73 import android.util.Log;
74 import android.view.Display;
75 import android.view.Gravity;
76 import android.view.HapticFeedbackConstants;
77 import android.view.KeyEvent;
78 import android.view.LayoutInflater;
79 import android.view.Menu;
80 import android.view.MotionEvent;
81 import android.view.Surface;
82 import android.view.View.OnClickListener;
83 import android.view.View.OnLongClickListener;
84 import android.view.View;
85 import android.view.ViewAnimationUtils;
86 import android.view.ViewGroup;
87 import android.view.ViewTreeObserver;
88 import android.view.Window;
89 import android.view.WindowManager;
90 import android.view.accessibility.AccessibilityEvent;
91 import android.view.animation.AccelerateInterpolator;
92 import android.view.animation.DecelerateInterpolator;
93 import android.view.inputmethod.InputMethodManager;
94 import android.widget.Advanceable;
95 import android.widget.FrameLayout;
96 import android.widget.ImageView;
97 import android.widget.TextView;
98 import android.widget.Toast;
99 import com.android.launcher3.DropTarget.DragObject;
100 import com.android.launcher3.PagedView.PageSwitchListener;
101 import com.android.launcher3.compat.AppWidgetManagerCompat;
102 import com.android.launcher3.compat.LauncherActivityInfoCompat;
103 import com.android.launcher3.compat.LauncherAppsCompat;
104 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
105 import com.android.launcher3.compat.PackageInstallerCompat;
106 import com.android.launcher3.compat.UserHandleCompat;
107 import com.android.launcher3.compat.UserManagerCompat;
108 import java.io.DataInputStream;
109 import java.io.DataOutputStream;
110 import java.io.File;
111 import java.io.FileDescriptor;
112 import java.io.FileNotFoundException;
113 import java.io.FileOutputStream;
114 import java.io.IOException;
115 import java.io.PrintWriter;
116 import java.lang.reflect.Field;
117 import java.lang.reflect.InvocationTargetException;
118 import java.lang.reflect.Method;
119 import java.text.DateFormat;
120 import java.util.ArrayList;
121 import java.util.Collection;
122 import java.util.Date;
123 import java.util.HashMap;
124 import java.util.List;
125 import java.util.concurrent.atomic.AtomicInteger;
126
127
128 interface DebugIntents {
129 public static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE";
130
131 public static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE";
132 }
133
134 /**
135 * Default launcher application.
136 */
137 public class Launcher extends Activity implements View.OnClickListener , OnLongClickListener , LauncherMo🔵
138 static final String TAG = "Launcher";
139
140 static final boolean LOGD = false;
141
142 static final boolean PROFILE_STARTUP = false;
143
144 static final boolean DEBUG_WIDGETS = false;
145
146 static final boolean DEBUG_STRICT_MODE = false;
147
148 static final boolean DEBUG_RESUME_TIME = false;
149
150 static final boolean DEBUG_DUMP_LOG = false;
151
152 // allow DebugIntents to run
153 static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run
154
155 private static final int REQUEST_CREATE_SHORTCUT = 1;
156
157 private static final int REQUEST_CREATE_APPWIDGET = 5;
158
159 private static final int REQUEST_PICK_SHORTCUT = 7;
160
161 private static final int REQUEST_PICK_APPWIDGET = 9;
162
163 private static final int REQUEST_PICK_WALLPAPER = 10;
164
165 private static final int REQUEST_BIND_APPWIDGET = 11;
166
167 private static final int REQUEST_RECONFIGURE_APPWIDGET = 12;
168
169 /**
170 * IntentStarter uses request codes starting with this. This must be greater than all activity
171 * request codes used internally.
172 */
173 protected static final int REQUEST_LAST = 100;
174
175 static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
176
177 static final int SCREEN_COUNT = 5;
178
179 static final int DEFAULT_SCREEN = 2;
180
181 // To turn on these properties, type
182 // adb shell setprop log.tag.PROPERTY_NAME [VERBOSE | SUPPRESS]
183 static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
184
185 static final String DUMP_STATE_PROPERTY = "launcher_dump_state";
186
187 static final String DISABLE_ALL_APPS_PROPERTY = "launcher_noallapps";
188
189 // The Intent extra that defines whether to ignore the launch animation
190 // The Intent extra that defines whether to ignore the launch animation
191 static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
192 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
193
194 // Type: int
195 // Type: int
196 private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
197
198 // Type: int
199 // Type: int
200 private static final String RUNTIME_STATE = "launcher.state";
201
202 // Type: int
203 // Type: int
204 private static final String RUNTIME_STATE_PENDING_ADD_CONTAINER = "launcher.add_container";
205
206 // Type: int
207 // Type: int
208 private static final String RUNTIME_STATE_PENDING_ADD_SCREEN = "launcher.add_screen";
209
210 // Type: int
211 // Type: int
212 private static final String RUNTIME_STATE_PENDING_ADD_CELL_X = "launcher.add_cell_x";
213
214 // Type: int
215 // Type: int
216 private static final String RUNTIME_STATE_PENDING_ADD_CELL_Y = "launcher.add_cell_y";
217
218 // Type: boolean
219 // Type: boolean
220 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
221
222 // Type: long
223 // Type: long
224 private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
225
226 // Type: int
227 // Type: int
228 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_X = "launcher.add_span_x";
229
230 // Type: int
231 // Type: int
232 private static final String RUNTIME_STATE_PENDING_ADD_SPAN_Y = "launcher.add_span_y";
233
234 // Type: parcelable
235 // Type: parcelable
236 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_INFO = "launcher.add_widget_info";
237
238 // Type: parcelable
239 // Type: parcelable
240 private static final String RUNTIME_STATE_PENDING_ADD_WIDGET_ID = "launcher.add_widget_id";
241
242 // Type: int[]
243 // Type: int[]
244 private static final String RUNTIME_STATE_VIEW_IDS = "launcher.view_ids";
245
246 static final String INTRO_SCREEN_DISMISSED = "launcher.intro_screen_dismissed";
247
248 static final String FIRST_RUN_ACTIVITY_DISPLAYED = "launcher.first_run_activity_displayed";
249
250 static final String FIRST_LOAD_COMPLETE = "launcher.first_load_complete";
251
252 static final String ACTION_FIRST_LOAD_COMPLETE =
253 "com.android.launcher3.action.FIRST_LOAD_COMPLETE";
254
255 private static final String TOOLBAR_ICON_METADATA_NAME = "com.android.launcher.toolbar_icon";
256
257 private static final String TOOLBAR_SEARCH_ICON_METADATA_NAME =
258 "com.android.launcher.toolbar_search_icon";
259
260 private static final String TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME =
261 "com.android.launcher.toolbar_voice_search_icon";
262
263 public static final String SHOW_WEIGHT_WATCHER = "debug.show_mem";
264
265 public static final boolean SHOW_WEIGHT_WATCHER_DEFAULT = false;
266
267 public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
268
269 /**
270 * The different states that Launcher can be in.
271 */
272 private enum State {
273
274 NONE,
275 WORKSPACE,
276 APPS_CUSTOMIZE,
277 APPS_CUSTOMIZE_SPRING_LOADED;}
278
279 private State mState = State.WORKSPACE;
280
281 private AnimatorSet mStateAnimation;
282
283 private boolean mIsSafeModeEnabled;
284
285 static final int APPWIDGET_HOST_ID = 1024;
286
287 public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
288
289 private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
290
291 private static final int ACTIVITY_START_DELAY = 1000;
292
293 private static final Object sLock = new Object();
294
295 private static int sScreen = DEFAULT_SCREEN;
296
297 private HashMap<Integer, Integer> mItemIdToViewId = new HashMap<Integer, Integer>();
298
299 private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
300
301 // How long to wait before the new-shortcut animation automatically pans the workspace
302 // How long to wait before the new-shortcut animation automatically pans the workspace
303 private static int NEW_APPS_PAGE_MOVE_DELAY = 500;
304
305 private static int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
306
307 private static int NEW_APPS_ANIMATION_DELAY = 500;
308
309 private static final int SINGLE_FRAME_DELAY = 16;
310
311 private final BroadcastReceiver mCloseSystemDialogsReceiver
312 = new CloseSystemDialogsIntentReceiver();
313
314 private final ContentObserver mWidgetObserver = new AppWidgetResetObserver();
315
316 private LayoutInflater mInflater;
317
318 private Workspace mWorkspace;
319
320 private View mLauncherView;
321
322 private View mPageIndicators;
323
324 private DragLayer mDragLayer;
325
326 private DragController mDragController;
327
328 private View mWeightWatcher;
329
330 private AppWidgetManagerCompat mAppWidgetManager;
331
332 private LauncherAppWidgetHost mAppWidgetHost;
333
334 private ItemInfo mPendingAddInfo = new ItemInfo();
335
336 private AppWidgetProviderInfo mPendingAddWidgetInfo;
337
338 private int mPendingAddWidgetId = -1;
339
340 private int[] mTmpAddItemCellCoordinates = new int[2];
341
342 private FolderInfo mFolderInfo;
343
344 private Hotseat mHotseat;
345
346 private ViewGroup mOverviewPanel;
347
348 private View mAllAppsButton;
349
350 private SearchDropTargetBar mSearchDropTargetBar;
351
352 private AppsCustomizeTabHost mAppsCustomizeTabHost;
353
354 private AppsCustomizePagedView mAppsCustomizeContent;
355
356 private boolean mAutoAdvanceRunning = false;
357
358 private View mQsb;
359
360 private Bundle mSavedState;
361
362 // We set the state in both onCreate and then onNewIntent in some cases, which causes both
363 // scroll issues (because the workspace may not have been measured yet) and extra work.
364 // Instead, just save the state that we need to restore Launcher to, and commit it in onResume.
365 private State mOnResumeState = State.NONE;
366
367 private SpannableStringBuilder mDefaultKeySsb = null;
368
369 private boolean mWorkspaceLoading = true;
370
371 private boolean mPaused = true;
372
373 private boolean mRestoring;
374
375 private boolean mWaitingForResult;
376
377 private boolean mOnResumeNeedsLoad;
378
379 private ArrayList<Runnable> mBindOnResumeCallbacks = new ArrayList<Runnable>();
380
381 private ArrayList<Runnable> mOnResumeCallbacks = new ArrayList<Runnable>();
382
383 private Bundle mSavedInstanceState;
384
385 private LauncherModel mModel;
386
387 private IconCache mIconCache;
388
389 private boolean mUserPresent = true;
390
391 private boolean mVisible = false;
392
393 private boolean mHasFocus = false;
394
395 private boolean mAttached = false;
396
397 private static LocaleConfiguration sLocaleConfiguration = null;
398
399 private static HashMap<Long, FolderInfo> sFolders = new HashMap<Long, FolderInfo>();
400
401 private View.OnTouchListener mHapticFeedbackTouchListener;
402
403 // Related to the auto-advancing of widgets
404 // Related to the auto-advancing of widgets
405 private final int ADVANCE_MSG = 1;
406
407 private final int mAdvanceInterval = 20000;
408
409 private final int mAdvanceStagger = 250;
410
411 private long mAutoAdvanceSentTime;
412
413 private long mAutoAdvanceTimeLeft = -1;
414
415 private HashMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new HashMap<View, AppWidgetProviderI🔵
416
417 // Determines how long to wait after a rotation before restoring the screen orientation to
418 // match the sensor state.
419 // Determines how long to wait after a rotation before restoring the screen orientation to
420 // match the sensor state.
421 private final int mRestoreScreenOrientationDelay = 500;
422
423 // External icons saved in case of resource changes, orientation, etc.
424 private static Drawable.ConstantState[] sGlobalSearchIcon = new Drawable.ConstantState[2];
425
426 private static Drawable.ConstantState[] sVoiceSearchIcon = new Drawable.ConstantState[2];
427
428 private Drawable mWorkspaceBackgroundDrawable;
429
430 private final ArrayList<Integer> mSynchronouslyBoundPages = new ArrayList<Integer>();
431
432 private static final boolean DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE = false;
433
434 static final ArrayList<String> sDumpLogs = new ArrayList<String>();
435
436 static Date sDateStamp = new Date();
437
438 static DateFormat sDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
439
440 static long sRunStart = System.currentTimeMillis();
441
442 static final String CORRUPTION_EMAIL_SENT_KEY = "corruptionEmailSent";
443
444 // We only want to get the SharedPreferences once since it does an FS stat each time we get
445 // it from the context.
446 // We only want to get the SharedPreferences once since it does an FS stat each time we get
447 // it from the context.
448 private SharedPreferences mSharedPrefs;
449
450 private static ArrayList<ComponentName> mIntentsOnWorkspaceFromUpgradePath = null;
451
452 // Holds the page that we need to animate to, and the icon views that we need to animate up
453 // when we scroll to that page on resume.
454 private ImageView mFolderIconImageView;
455
456 private Bitmap mFolderIconBitmap;
457
458 private Canvas mFolderIconCanvas;
459
460 private Rect mRectForFolderAnimation = new Rect();
461
462 private BubbleTextView mWaitingForResume;
463
464 private Runnable mBuildLayersRunnable = new Runnable() {
465 public void run() {
466 if (mWorkspace != null) {
467 mWorkspace.buildPageHardwareLayers();
468 }
469 }
470 };
471
472 private static PendingAddArguments sPendingAddItem;
473
474 public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
475
476 private static class PendingAddArguments {
477 int requestCode;
478
479 Intent intent;
480
481 long container;
482
483 long screenId;
484
485 int cellX;
486
487 int cellY;
488
489 int appWidgetId;
490 }
491
492 private Stats mStats;
493
494 FocusIndicatorView mFocusHandler;
495
496 static boolean isPropertyEnabled(String propertyName) {
497 return Log.isLoggable(propertyName, Log.VERBOSE);
498 }
499
500 @Override
501 protected void onCreate(Bundle savedInstanceState) {
502 if (DEBUG_STRICT_MODE) {
503 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
504 .detectDiskReads()
505 .detectDiskWrites()
506 .detectNetwork() // or .detectAll() for all detectable problems
507 .penaltyLog()
508 .build());
509 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
510 .detectLeakedSqlLiteObjects()
511 .detectLeakedClosableObjects()
512 .penaltyLog()
513 .penaltyDeath()
514 .build());
515 }
516
517 super.onCreate(savedInstanceState);
518
519 LauncherAppState.setApplicationContext(getApplicationContext());
520 LauncherAppState app = LauncherAppState.getInstance();
521 LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);
522 // Determine the dynamic grid properties
523 Point smallestSize = new Point();
524 Point largestSize = new Point();
525 Point realSize = new Point();
526 Display display = getWindowManager().getDefaultDisplay();
527 display.getCurrentSizeRange(smallestSize, largestSize);
528 display.getRealSize(realSize);
529 DisplayMetrics dm = new DisplayMetrics();
530 display.getMetrics(dm);
531
532 // Lazy-initialize the dynamic grid
533 DeviceProfile grid = app.initDynamicGrid(this,
534 Math.min(smallestSize.x, smallestSize.y),
535 Math.min(largestSize.x, largestSize.y),
536 realSize.x, realSize.y,
537 dm.widthPixels, dm.heightPixels);
538
539 // the LauncherApplication should call this, but in case of Instrumentation it might not be prese🔵
540 mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
541 Context.MODE_PRIVATE);
542 mIsSafeModeEnabled = getPackageManager().isSafeMode();
543 mModel = app.setLauncher(this);
544 mIconCache = app.getIconCache();
545 mIconCache.flushInvalidIcons(grid);
546 mDragController = new DragController(this);
547 mInflater = getLayoutInflater();
548
549 mStats = new Stats(this);
550
551 mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
552
553 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
554 mAppWidgetHost.startListening();
555
556 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
557 // this also ensures that any synchronous binding below doesn't re-trigger another
558 // LauncherModel load.
559 mPaused = false;
560
561 if (PROFILE_STARTUP) {
562 android.os.Debug.startMethodTracing(
563 Environment.getExternalStorageDirectory() + "/launcher");
564 }
565
566 checkForLocaleChange();
567 setContentView(R.layout.launcher);
568
569 setupViews();
570 grid.layout(this);
571
572 registerContentObservers();
573
574 lockAllApps();
575
576 mSavedState = savedInstanceState;
577 restoreState(mSavedState);
578
579 if (PROFILE_STARTUP) {
580 android.os.Debug.stopMethodTracing();
581 }
582
583 if (!mRestoring) {
584 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
585 // If the user leaves launcher, then we should just load items asynchronously when
586 // they return.
587 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
588 } else {
589 // We only load the page synchronously if the user rotates (or triggers a
590 // configuration change) while launcher is in the foreground
591 mModel.startLoader(true, mWorkspace.getRestorePage());
592 }
593 }
594
595 // For handling default keys
596 mDefaultKeySsb = new SpannableStringBuilder();
597 Selection.setSelection(mDefaultKeySsb, 0);
598
599 IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
600 registerReceiver(mCloseSystemDialogsReceiver, filter);
601
602 updateGlobalIcons();
603
604 // On large interfaces, we want the screen to auto-rotate based on the current orientation
605 unlockScreenOrientation(true);
606
607 if (shouldShowIntroScreen()) {
608 showIntroScreen();
609 } else {
610 showFirstRunActivity();
611 showFirstRunClings();
612 }
613 }
614
615 @Override
616 public void onLauncherProviderChange() { }
617
618 /** To be overriden by subclasses to hint to Launcher that we have custom content */
619 protected boolean hasCustomContentToLeft() {
620 return false;
621 }
622
623 /**
624 * To be overridden by subclasses to populate the custom content container and call
625 * {@link #addToCustomContentPage}. This will only be invoked if
626 * {@link #hasCustomContentToLeft()} is {@code true}.
627 */
628 protected void populateCustomContentContainer() {
629 }
630
631 /**
632 * Invoked by subclasses to signal a change to the {@link #addCustomContentToLeft} value to
633 * ensure the custom content page is added or removed if necessary.
634 */
635 protected void invalidateHasCustomContentToLeft() {
636 if (mWorkspace == null || mWorkspace.getScreenOrder().isEmpty()) {
637 // Not bound yet, wait for bindScreens to be called.
638 return;
639 }
640
641 if (!mWorkspace.hasCustomContent() && hasCustomContentToLeft()) {
642 // Create the custom content page and call the subclass to populate it.
643 mWorkspace.createCustomContentContainer();
644 populateCustomContentContainer();
645 } else if (mWorkspace.hasCustomContent() && !hasCustomContentToLeft()) {
646 mWorkspace.removeCustomContentPage();
647 }
648 }
649
650 private void updateGlobalIcons() {
651 boolean searchVisible = false;
652 boolean voiceVisible = false;
653 // If we have a saved version of these external icons, we load them up immediately
654 int coi = getCurrentOrientationIndexForGlobalIcons();
655 if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null) {
656 searchVisible = updateGlobalSearchIcon();
657 voiceVisible = updateVoiceSearchIcon(searchVisible);
658 }
659 if (sGlobalSearchIcon[coi] != null) {
660 updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
661 searchVisible = true;
662 }
663 if (sVoiceSearchIcon[coi] != null) {
664 updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
665 voiceVisible = true;
666 }
667 if (mSearchDropTargetBar != null) {
668 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
669 }
670 }
671
672 private void checkForLocaleChange() {
673 if (sLocaleConfiguration == null) {
674 new AsyncTask<Void, Void, LocaleConfiguration>() {
675 @Override
676 protected LocaleConfiguration doInBackground(Void... unused) {
677 LocaleConfiguration localeConfiguration = new LocaleConfiguration();
678 readConfiguration(Launcher.this, localeConfiguration);
679 return localeConfiguration;
680 }
681
682 @Override
683 protected void onPostExecute(LocaleConfiguration result) {
684 sLocaleConfiguration = result;
685 checkForLocaleChange(); // recursive, but now with a locale configuration
686 }
687 }.execute();
688 return;
689 }
690
691 final Configuration configuration = getResources().getConfiguration();
692
693 final String previousLocale = sLocaleConfiguration.locale;
694 final String locale = configuration.locale.toString();
695
696 final int previousMcc = sLocaleConfiguration.mcc;
697 final int mcc = configuration.mcc;
698
699 final int previousMnc = sLocaleConfiguration.mnc;
700 final int mnc = configuration.mnc;
701
702 boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMn🔵
703
704 if (localeChanged) {
705 sLocaleConfiguration.locale = locale;
706 sLocaleConfiguration.mcc = mcc;
707 sLocaleConfiguration.mnc = mnc;
708
709 mIconCache.flush();
710
711 final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
712 new AsyncTask<Void, Void, Void>() {
713 public Void doInBackground(Void ... args) {
714 writeConfiguration(Launcher.this, localeConfiguration);
715 return null;
716 }
717 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
718 }
719 }
720
721 private static class LocaleConfiguration {
722 public String locale;
723
724 public int mcc = -1;
725
726 public int mnc = -1;
727 }
728
729 private static void readConfiguration(Context context, LocaleConfiguration configuration) {
730 DataInputStream in = null;
731 try {
732 in = new DataInputStream(context.openFileInput(LauncherFiles.LAUNCHER_PREFS));
733 configuration.locale = in.readUTF();
734 configuration.mcc = in.readInt();
735 configuration.mnc = in.readInt();
736 } catch (FileNotFoundException e) {
737 // Ignore
738 } catch (IOException e) {
739 // Ignore
740 } finally {
741 if (in != null) {
742 try {
743 in.close();
744 } catch (IOException e) {
745 // Ignore
746 }
747 }
748 }
749 }
750
751 private static void writeConfiguration(Context context, LocaleConfiguration configuration) {
752 DataOutputStream out = null;
753 try {
754 out = new DataOutputStream(context.openFileOutput(LauncherFiles.LAUNCHER_PREFS, MODE_PRIVATE)🔵
755 out.writeUTF(configuration.locale);
756 out.writeInt(configuration.mcc);
757 out.writeInt(configuration.mnc);
758 out.flush();
759 } catch (FileNotFoundException e) {
760 // Ignore
761 } catch (IOException e) {
762 //noinspection ResultOfMethodCallIgnored
763 context.getFileStreamPath(LauncherFiles.LAUNCHER_PREFS).delete();
764 } finally {
765 if (out != null) {
766 try {
767 out.close();
768 } catch (IOException e) {
769 // Ignore
770 }
771 }
772 }
773 }
774
775 public Stats getStats() {
776 return mStats;
777 }
778
779 public LayoutInflater getInflater() {
780 return mInflater;
781 }
782
783 boolean isDraggingEnabled() {
784 // We prevent dragging when we are loading the workspace as it is possible to pick up a view
785 // that is subsequently removed from the workspace in startBinding().
786 return !mModel.isLoadingWorkspace();
787 }
788
789 static int getScreen() {
790 synchronized (sLock) {
791 return sScreen;
792 }
793 }
794
795 static void setScreen(int screen) {
796 synchronized (sLock) {
797 sScreen = screen;
798 }
799 }
800
801 public static int generateViewId() {
802 if (Build.VERSION.SDK_INT >= 17) {
803 return View.generateViewId();
804 } else {
805 // View.generateViewId() is not available. The following fallback logic is a copy
806 // of its implementation.
807 for (;;) {
808 final int result = sNextGeneratedId.get();
809 // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
810 int newValue = result + 1;
811 if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
812 if (sNextGeneratedId.compareAndSet(result, newValue)) {
813 return result;
814 }
815 }
816 }
817 }
818
819 public int getViewIdForItem(ItemInfo info) {
820 // This cast is safe given the > 2B range for int.
821 int itemId = (int) info.id;
822 if (mItemIdToViewId.containsKey(itemId)) {
823 return mItemIdToViewId.get(itemId);
824 }
825 int viewId = generateViewId();
826 mItemIdToViewId.put(itemId, viewId);
827 return viewId;
828 }
829
830 /**
831 * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
832 * a configuration step, this allows the proper animations to run after other transitions.
833 */
834 private long completeAdd(PendingAddArguments args) {
835 long screenId = args.screenId;
836 if (args.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
837 // When the screen id represents an actual screen (as opposed to a rank) we make sure
838 // that the drop page actually exists.
839 screenId = ensurePendingDropLayoutExists(args.screenId);
840 }
841
842 switch (args.requestCode) {
843 case REQUEST_CREATE_SHORTCUT:
844 completeAddShortcut(args.intent, args.container, screenId, args.cellX,
845 args.cellY);
846 break;
847 case REQUEST_CREATE_APPWIDGET:
848 completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
849 break;
850 case REQUEST_RECONFIGURE_APPWIDGET:
851 completeRestoreAppWidget(args.appWidgetId);
852 break;
853 }
854 // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
855 // if you turned the screen off and then back while in All Apps, Launcher would not
856 // return to the workspace. Clearing mAddInfo.container here fixes this issue
857 resetAddInfo();
858 return screenId;
859 }
860
861 @Override
862 protected void onActivityResult(
863 final int requestCode, final int resultCode, final Intent data) {
864 // Reset the startActivity waiting flag
865 setWaitingForResult(false);
866 final int pendingAddWidgetId = mPendingAddWidgetId;
867 mPendingAddWidgetId = -1;
868
869 Runnable exitSpringLoaded = new Runnable() {
870 @Override
871 public void run() {
872 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
873 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
874 }
875 };
876
877 if (requestCode == REQUEST_BIND_APPWIDGET) {
878 final int appWidgetId = data != null ?
879 data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
880 if (resultCode == RESULT_CANCELED) {
881 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
882 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
883 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
884 } else if (resultCode == RESULT_OK) {
885 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
886 mPendingAddWidgetInfo, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
887 }
888 return;
889 } else if (requestCode == REQUEST_PICK_WALLPAPER) {
890 if (resultCode == RESULT_OK && mWorkspace.isInOverviewMode()) {
891 mWorkspace.exitOverviewMode(false);
892 }
893 return;
894 }
895
896 boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
897 requestCode == REQUEST_CREATE_APPWIDGET);
898
899 final boolean workspaceLocked = isWorkspaceLocked();
900 // We have special handling for widgets
901 if (isWidgetDrop) {
902 final int appWidgetId;
903 int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
904 : -1;
905 if (widgetId < 0) {
906 appWidgetId = pendingAddWidgetId;
907 } else {
908 appWidgetId = widgetId;
909 }
910
911 final int result;
912 if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
913 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
914 "returned from the widget configuration activity.");
915 result = RESULT_CANCELED;
916 completeTwoStageWidgetDrop(result, appWidgetId);
917 final Runnable onComplete = new Runnable() {
918 @Override
919 public void run() {
920 exitSpringLoadedDragModeDelayed(false, 0, null);
921 }
922 };
923 if (workspaceLocked) {
924 // No need to remove the empty screen if we're mid-binding, as the
925 // the bind will not add the empty screen.
926 mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
927 } else {
928 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
929 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
930 }
931 } else {
932 if (!workspaceLocked) {
933 if (mPendingAddInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
934 // When the screen id represents an actual screen (as opposed to a rank)
935 // we make sure that the drop page actually exists.
936 mPendingAddInfo.screenId =
937 ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
938 }
939 final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
940
941 dropLayout.setDropPending(true);
942 final Runnable onComplete = new Runnable() {
943 @Override
944 public void run() {
945 completeTwoStageWidgetDrop(resultCode, appWidgetId);
946 dropLayout.setDropPending(false);
947 }
948 };
949 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
950 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
951 } else {
952 PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
953 mPendingAddInfo);
954 sPendingAddItem = args;
955 }
956 }
957 return;
958 }
959
960 if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) {
961 if (resultCode == RESULT_OK) {
962 // Update the widget view.
963 PendingAddArguments args = preparePendingAddArgs(requestCode, data,
964 pendingAddWidgetId, mPendingAddInfo);
965 if (workspaceLocked) {
966 sPendingAddItem = args;
967 } else {
968 completeAdd(args);
969 }
970 }
971 // Leave the widget in the pending state if the user canceled the configure.
972 return;
973 }
974
975 // The pattern used here is that a user PICKs a specific application,
976 // which, depending on the target, might need to CREATE the actual target.
977
978 // For example, the user would PICK_SHORTCUT for "Music playlist", and we
979 // launch over to the Music app to actually CREATE_SHORTCUT.
980 if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
981 final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
982 mPendingAddInfo);
983 if (isWorkspaceLocked()) {
984 sPendingAddItem = args;
985 } else {
986 completeAdd(args);
987 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
988 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
989 }
990 } else if (resultCode == RESULT_CANCELED) {
991 mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
992 ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
993 }
994 mDragLayer.clearAnimatedView();
995 }
996
997 private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
998 appWidgetId, ItemInfo info) {
999 PendingAddArguments args = new PendingAddArguments();
1000 args.requestCode = requestCode;
1001 args.intent = data;
1002 args.container = info.container;
1003 args.screenId = info.screenId;
1004 args.cellX = info.cellX;
1005 args.cellY = info.cellY;
1006 args.appWidgetId = appWidgetId;
1007 return args;
1008 }
1009
1010 /**
1011 * Check to see if a given screen id exists. If not, create it at the end, return the new id.
1012 *
1013 * @param screenId the screen id to check
1014 * @return the new screen, or screenId if it exists
1015 */
1016 private long ensurePendingDropLayoutExists(long screenId) {
1017 CellLayout dropLayout =
1018 (CellLayout) mWorkspace.getScreenWithId(screenId);
1019 if (dropLayout == null) {
1020 // it's possible that the add screen was removed because it was
1021 // empty and a re-bind occurred
1022 mWorkspace.addExtraEmptyScreen();
1023 return mWorkspace.commitExtraEmptyScreen();
1024 } else {
1025 return screenId;
1026 }
1027 }
1028
1029 private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
1030 CellLayout cellLayout =
1031 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
1032 Runnable onCompleteRunnable = null;
1033 int animationType = 0;
1034
1035 AppWidgetHostView boundWidget = null;
1036 if (resultCode == RESULT_OK) {
1037 animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
1038 final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
1039 mPendingAddWidgetInfo);
1040 boundWidget = layout;
1041 onCompleteRunnable = new Runnable() {
1042 @Override
1043 public void run() {
1044 completeAddAppWidget(appWidgetId, mPendingAddInfo.container,
1045 mPendingAddInfo.screenId, layout, null);
1046 exitSpringLoadedDragModeDelayed((resultCode != RESULT_CANCELED),
1047 EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
1048 }
1049 };
1050 } else if (resultCode == RESULT_CANCELED) {
1051 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1052 animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
1053 }
1054 if (mDragLayer.getAnimatedView() != null) {
1055 mWorkspace.animateWidgetDrop(mPendingAddInfo, cellLayout,
1056 (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
1057 animationType, boundWidget, true);
1058 } else if (onCompleteRunnable != null) {
1059 // The animated view may be null in the case of a rotation during widget configuration
1060 onCompleteRunnable.run();
1061 }
1062 }
1063
1064 @Override
1065 protected void onStop() {
1066 super.onStop();
1067 FirstFrameAnimatorHelper.setIsVisible(false);
1068 }
1069
1070 @Override
1071 protected void onStart() {
1072 super.onStart();
1073 FirstFrameAnimatorHelper.setIsVisible(true);
1074 }
1075
1076 @Override
1077 protected void onResume() {
1078 long startTime = 0;
1079 if (DEBUG_RESUME_TIME) {
1080 startTime = System.currentTimeMillis();
1081 Log.v(TAG, "Launcher.onResume()");
1082 }
1083 super.onResume();
1084
1085 // Restore the previous launcher state
1086 if (mOnResumeState == State.WORKSPACE) {
1087 showWorkspace(false);
1088 } else if (mOnResumeState == State.APPS_CUSTOMIZE) {
1089 showAllApps(false, mAppsCustomizeContent.getContentType(), false);
1090 }
1091 mOnResumeState = State.NONE;
1092
1093 // Background was set to gradient in onPause(), restore to black if in all apps.
1094 setWorkspaceBackground(mState == State.WORKSPACE);
1095
1096 mPaused = false;
1097 if (mRestoring || mOnResumeNeedsLoad) {
1098 setWorkspaceLoading(true);
1099 mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
1100 mRestoring = false;
1101 mOnResumeNeedsLoad = false;
1102 }
1103 if (mBindOnResumeCallbacks.size() > 0) {
1104 // We might have postponed some bind calls until onResume (see waitUntilResume) --
1105 // execute them here
1106 long startTimeCallbacks = 0;
1107 if (DEBUG_RESUME_TIME) {
1108 startTimeCallbacks = System.currentTimeMillis();
1109 }
1110
1111 if (mAppsCustomizeContent != null) {
1112 mAppsCustomizeContent.setBulkBind(true);
1113 }
1114 for (int i = 0; i < mBindOnResumeCallbacks.size(); i++) {
1115 mBindOnResumeCallbacks.get(i).run();
1116 }
1117 if (mAppsCustomizeContent != null) {
1118 mAppsCustomizeContent.setBulkBind(false);
1119 }
1120 mBindOnResumeCallbacks.clear();
1121 if (DEBUG_RESUME_TIME) {
1122 Log.d(TAG, "Time spent processing callbacks in onResume: " +
1123 (System.currentTimeMillis() - startTimeCallbacks));
1124 }
1125 }
1126 if (mOnResumeCallbacks.size() > 0) {
1127 for (int i = 0; i < mOnResumeCallbacks.size(); i++) {
1128 mOnResumeCallbacks.get(i).run();
1129 }
1130 mOnResumeCallbacks.clear();
1131 }
1132
1133 // Reset the pressed state of icons that were locked in the press state while activities
1134 // were launching
1135 if (mWaitingForResume != null) {
1136 // Resets the previous workspace icon press state
1137 mWaitingForResume.setStayPressed(false);
1138 }
1139
1140 // It is possible that widgets can receive updates while launcher is not in the foreground.
1141 // Consequently, the widgets will be inflated in the orientation of the foreground activity
1142 // (framework issue). On resuming, we ensure that any widgets are inflated for the current
1143 // orientation.
1144 getWorkspace().reinflateWidgetsIfNecessary();
1145
1146 // Process any items that were added while Launcher was away.
1147 InstallShortcutReceiver.disableAndFlushInstallQueue(this);
1148
1149 // Update the voice search button proxy
1150 updateVoiceButtonProxyVisible(false);
1151
1152 // Again, as with the above scenario, it's possible that one or more of the global icons
1153 // were updated in the wrong orientation.
1154 updateGlobalIcons();
1155 if (DEBUG_RESUME_TIME) {
1156 Log.d(TAG, "Time spent in onResume: " + (System.currentTimeMillis() - startTime));
1157 }
1158
1159 if (mWorkspace.getCustomContentCallbacks() != null) {
1160 // If we are resuming and the custom content is the current page, we call onShow().
1161 // It is also poassible that onShow will instead be called slightly after first layout
1162 // if PagedView#setRestorePage was set to the custom content page in onCreate().
1163 if (mWorkspace.isOnOrMovingToCustomContent()) {
1164 mWorkspace.getCustomContentCallbacks().onShow(true);
1165 }
1166 }
1167 mWorkspace.updateInteractionForState();
1168 mWorkspace.onResume();
1169
1170 PackageInstallerCompat.getInstance(this).onResume();
1171 }
1172
1173 @Override
1174 protected void onPause() {
1175 // Ensure that items added to Launcher are queued until Launcher returns
1176 InstallShortcutReceiver.enableInstallQueue();
1177 PackageInstallerCompat.getInstance(this).onPause();
1178
1179 super.onPause();
1180 mPaused = true;
1181 mDragController.cancelDrag();
1182 mDragController.resetLastGestureUpTime();
1183
1184 // We call onHide() aggressively. The custom content callbacks should be able to
1185 // debounce excess onHide calls.
1186 if (mWorkspace.getCustomContentCallbacks() != null) {
1187 mWorkspace.getCustomContentCallbacks().onHide();
1188 }
1189 }
1190
1191 QSBScroller mQsbScroller = new QSBScroller() {
1192 int scrollY = 0;
1193
1194 @Override
1195 public void setScrollY(int scroll) {
1196 scrollY = scroll;
1197
1198 if (mWorkspace.isOnOrMovingToCustomContent()) {
1199 mSearchDropTargetBar.setTranslationY(- scrollY);
1200 getQsbBar().setTranslationY(-scrollY);
1201 }
1202 }
1203 };
1204
1205 public void resetQSBScroll() {
1206 mSearchDropTargetBar.animate().translationY(0).start();
1207 getQsbBar().animate().translationY(0).start();
1208 }
1209
1210 public interface CustomContentCallbacks {
1211 // Custom content is completely shown. {@code fromResume} indicates whether this was caused
1212 // by a onResume or by scrolling otherwise.
1213 public abstract void onShow(boolean fromResume);
1214
1215 // Custom content is completely hidden
1216 public abstract void onHide();
1217
1218 // Custom content scroll progress changed. From 0 (not showing) to 1 (fully showing).
1219 public abstract void onScrollProgressChanged(float progress);
1220
1221 // Indicates whether the user is allowed to scroll away from the custom content.
1222 public abstract boolean isScrollingAllowed();
1223 }
1224
1225 protected boolean hasSettings() {
1226 return false;
1227 }
1228
1229 public interface QSBScroller {
1230 public abstract void setScrollY(int scrollY);
1231 }
1232
1233 public QSBScroller addToCustomContentPage(View customContent,
1234 CustomContentCallbacks callbacks, String description) {
1235 mWorkspace.addToCustomContentPage(customContent, callbacks, description);
1236 return mQsbScroller;
1237 }
1238
1239 // The custom content needs to offset its content to account for the QSB
1240 public int getTopOffsetForCustomContent() {
1241 return mWorkspace.getPaddingTop();
1242 }
1243
1244 @Override
1245 public Object onRetainNonConfigurationInstance() {
1246 // Flag the loader to stop early before switching
1247 if (mModel.isCurrentCallbacks(this)) {
1248 mModel.stopLoader();
1249 }
1250 if (mAppsCustomizeContent != null) {
1251 mAppsCustomizeContent.surrender();
1252 }
1253 return Boolean.TRUE;
1254 }
1255
1256 // We can't hide the IME if it was forced open. So don't bother
1257 @Override
1258 public void onWindowFocusChanged(boolean hasFocus) {
1259 super.onWindowFocusChanged(hasFocus);
1260 mHasFocus = hasFocus;
1261 }
1262
1263 private boolean acceptFilter() {
1264 final InputMethodManager inputManager = (InputMethodManager)
1265 getSystemService(Context.INPUT_METHOD_SERVICE);
1266 return !inputManager.isFullscreenMode();
1267 }
1268
1269 @Override
1270 public boolean onKeyDown(int keyCode, KeyEvent event) {
1271 final int uniChar = event.getUnicodeChar();
1272 final boolean handled = super.onKeyDown(keyCode, event);
1273 final boolean isKeyNotWhitespace = uniChar > 0 && !Character.isWhitespace(uniChar);
1274 if (!handled && acceptFilter() && isKeyNotWhitespace) {
1275 boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
1276 keyCode, event);
1277 if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
1278 // something usable has been typed - start a search
1279 // the typed text will be retrieved and cleared by
1280 // showSearchDialog()
1281 // If there are multiple keystrokes before the search dialog takes focus,
1282 // onSearchRequested() will be called for every keystroke,
1283 // but it is idempotent, so it's fine.
1284 return onSearchRequested();
1285 }
1286 }
1287
1288 // Eat the long press event so the keyboard doesn't come up.
1289 if (keyCode == KeyEvent.KEYCODE_MENU && event.isLongPress()) {
1290 return true;
1291 }
1292
1293 return handled;
1294 }
1295
1296 private String getTypedText() {
1297 return mDefaultKeySsb.toString();
1298 }
1299
1300 private void clearTypedText() {
1301 mDefaultKeySsb.clear();
1302 mDefaultKeySsb.clearSpans();
1303 Selection.setSelection(mDefaultKeySsb, 0);
1304 }
1305
1306 /**
1307 * Given the integer (ordinal) value of a State enum instance, convert it to a variable of type
1308 * State
1309 */
1310 private static State intToState(int stateOrdinal) {
1311 State state = State.WORKSPACE;
1312 final State[] stateValues = State.values();
1313 for (int i = 0; i < stateValues.length; i++) {
1314 if (stateValues[i].ordinal() == stateOrdinal) {
1315 state = stateValues[i];
1316 break;
1317 }
1318 }
1319 return state;
1320 }
1321
1322 /**
1323 * Restores the previous state, if it exists.
1324 *
1325 * @param savedState The previous state.
1326 */
1327 @SuppressWarnings("unchecked")
1328 private void restoreState(Bundle savedState) {
1329 if (savedState == null) {
1330 return;
1331 }
1332
1333 State state = intToState(savedState.getInt(RUNTIME_STATE, State.WORKSPACE.ordinal()));
1334 if (state == State.APPS_CUSTOMIZE) {
1335 mOnResumeState = State.APPS_CUSTOMIZE;
1336 }
1337
1338 int currentScreen = savedState.getInt(RUNTIME_STATE_CURRENT_SCREEN,
1339 PagedView.INVALID_RESTORE_PAGE);
1340 if (currentScreen != PagedView.INVALID_RESTORE_PAGE) {
1341 mWorkspace.setRestorePage(currentScreen);
1342 }
1343
1344 final long pendingAddContainer = savedState.getLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, -1);
1345 final long pendingAddScreen = savedState.getLong(RUNTIME_STATE_PENDING_ADD_SCREEN, -1);
1346
1347 if (pendingAddContainer != ItemInfo.NO_ID && pendingAddScreen > -1) {
1348 mPendingAddInfo.container = pendingAddContainer;
1349 mPendingAddInfo.screenId = pendingAddScreen;
1350 mPendingAddInfo.cellX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_X);
1351 mPendingAddInfo.cellY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_CELL_Y);
1352 mPendingAddInfo.spanX = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_X);
1353 mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
1354 mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
1355 mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
1356 setWaitingForResult(true);
1357 mRestoring = true;
1358 }
1359
1360 boolean renameFolder = savedState.getBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, false);
1361 if (renameFolder) {
1362 long id = savedState.getLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID);
1363 mFolderInfo = mModel.getFolderById(this, sFolders, id);
1364 mRestoring = true;
1365 }
1366
1367 // Restore the AppsCustomize tab
1368 if (mAppsCustomizeTabHost != null) {
1369 String curTab = savedState.getString("apps_customize_currentTab");
1370 if (curTab != null) {
1371 mAppsCustomizeTabHost.setContentTypeImmediate(
1372 mAppsCustomizeTabHost.getContentTypeForTabTag(curTab));
1373 mAppsCustomizeContent.loadAssociatedPages(
1374 mAppsCustomizeContent.getCurrentPage());
1375 }
1376
1377 int currentIndex = savedState.getInt("apps_customize_currentIndex");
1378 mAppsCustomizeContent.restorePageForIndex(currentIndex);
1379 }
1380 mItemIdToViewId = (HashMap<Integer, Integer>)
1381 savedState.getSerializable(RUNTIME_STATE_VIEW_IDS);
1382 }
1383
1384 /**
1385 * Finds all the views we need and configure them properly.
1386 */
1387 private void setupViews() {
1388 final DragController dragController = mDragController;
1389
1390 mLauncherView = findViewById(R.id.launcher);
1391 mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
1392 mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
1393 mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
1394 mWorkspace.setPageSwitchListener(this);
1395 mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
1396
1397 mLauncherView.setSystemUiVisibility(
1398 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1399 mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
1400
1401 // Setup the drag layer
1402 mDragLayer.setup(this, dragController);
1403
1404 // Setup the hotseat
1405 mHotseat = (Hotseat) findViewById(R.id.hotseat);
1406 if (mHotseat != null) {
1407 mHotseat.setup(this);
1408 mHotseat.setOnLongClickListener(this);
1409 }
1410
1411 mOverviewPanel = (ViewGroup) findViewById(R.id.overview_panel);
1412 View widgetButton = findViewById(R.id.widget_button);
1413 widgetButton.setOnClickListener(new OnClickListener() {
1414 @Override
1415 public void onClick(View arg0) {
1416 if (!mWorkspace.isSwitchingState()) {
1417 onClickAddWidgetButton(arg0);
1418 }
1419 }
1420 });
1421 widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
1422
1423 View wallpaperButton = findViewById(R.id.wallpaper_button);
1424 wallpaperButton.setOnClickListener(new OnClickListener() {
1425 @Override
1426 public void onClick(View arg0) {
1427 if (!mWorkspace.isSwitchingState()) {
1428 onClickWallpaperPicker(arg0);
1429 }
1430 }
1431 });
1432 wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
1433
1434 View settingsButton = findViewById(R.id.settings_button);
1435 if (hasSettings()) {
1436 settingsButton.setOnClickListener(new OnClickListener() {
1437 @Override
1438 public void onClick(View arg0) {
1439 if (!mWorkspace.isSwitchingState()) {
1440 onClickSettingsButton(arg0);
1441 }
1442 }
1443 });
1444 settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
1445 } else {
1446 settingsButton.setVisibility(View.GONE);
1447 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams();
1448 lp.gravity = Gravity.END | Gravity.TOP;
1449 widgetButton.requestLayout();
1450 }
1451
1452 mOverviewPanel.setAlpha(0f);
1453
1454 // Setup the workspace
1455 mWorkspace.setHapticFeedbackEnabled(false);
1456 mWorkspace.setOnLongClickListener(this);
1457 mWorkspace.setup(dragController);
1458 dragController.addDragListener(mWorkspace);
1459
1460 // Get the search/delete bar
1461 mSearchDropTargetBar = (SearchDropTargetBar)
1462 mDragLayer.findViewById(R.id.search_drop_target_bar);
1463
1464 // Setup AppsCustomize
1465 mAppsCustomizeTabHost = (AppsCustomizeTabHost) findViewById(R.id.apps_customize_pane);
1466 mAppsCustomizeContent = (AppsCustomizePagedView)
1467 mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content);
1468 mAppsCustomizeContent.setup(this, dragController);
1469
1470 // Setup the drag controller (drop targets have to be added in reverse order in priority)
1471 dragController.setDragScoller(mWorkspace);
1472 dragController.setScrollView(mDragLayer);
1473 dragController.setMoveTarget(mWorkspace);
1474 dragController.addDropTarget(mWorkspace);
1475 if (mSearchDropTargetBar != null) {
1476 mSearchDropTargetBar.setup(this, dragController);
1477 }
1478
1479 if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
1480 Log.v(TAG, "adding WeightWatcher");
1481 mWeightWatcher = new WeightWatcher(this);
1482 mWeightWatcher.setAlpha(0.5f);
1483 ((FrameLayout) mLauncherView).addView(mWeightWatcher,
1484 new FrameLayout.LayoutParams(
1485 FrameLayout.LayoutParams.MATCH_PARENT,
1486 FrameLayout.LayoutParams.WRAP_CONTENT,
1487 Gravity.BOTTOM)
1488 );
1489
1490 boolean show = shouldShowWeightWatcher();
1491 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
1492 }
1493 }
1494
1495 /**
1496 * Sets the all apps button. This method is called from {@link Hotseat}.
1497 */
1498 public void setAllAppsButton(View allAppsButton) {
1499 mAllAppsButton = allAppsButton;
1500 }
1501
1502 public View getAllAppsButton() {
1503 return mAllAppsButton;
1504 }
1505
1506 /**
1507 * Creates a view representing a shortcut.
1508 *
1509 * @param info The data structure describing the shortcut.
1510 *
1511 * @return A View inflated from R.layout.application.
1512 */
1513 View createShortcut(ShortcutInfo info) {
1514 return createShortcut(R.layout.application,
1515 (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1516 }
1517
1518 /**
1519 * Creates a view representing a shortcut inflated from the specified resource.
1520 *
1521 * @param layoutResId The id of the XML layout used to create the shortcut.
1522 * @param parent The group the shortcut belongs to.
1523 * @param info The data structure describing the shortcut.
1524 *
1525 * @return A View inflated from layoutResId.
1526 */
1527 View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
1528 BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
1529 favorite.applyFromShortcutInfo(info, mIconCache, true);
1530 favorite.setOnClickListener(this);
1531 favorite.setOnFocusChangeListener(mFocusHandler);
1532 return favorite;
1533 }
1534
1535 /**
1536 * Add a shortcut to the workspace.
1537 *
1538 * @param data The intent describing the shortcut.
1539 * @param cellInfo The position on screen where to create the shortcut.
1540 */
1541 private void completeAddShortcut(Intent data, long container, long screenId, int cellX,
1542 int cellY) {
1543 int[] cellXY = mTmpAddItemCellCoordinates;
1544 int[] touchXY = mPendingAddInfo.dropPos;
1545 CellLayout layout = getCellLayout(container, screenId);
1546
1547 boolean foundCellSpan = false;
1548
1549 ShortcutInfo info = mModel.infoFromShortcutIntent(this, data, null);
1550 if (info == null) {
1551 return;
1552 }
1553 final View view = createShortcut(info);
1554
1555 // First we check if we already know the exact location where we want to add this item.
1556 if (cellX >= 0 && cellY >= 0) {
1557 cellXY[0] = cellX;
1558 cellXY[1] = cellY;
1559 foundCellSpan = true;
1560
1561 // If appropriate, either create a folder or add to an existing folder
1562 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1563 true, null,null)) {
1564 return;
1565 }
1566 DragObject dragObject = new DragObject();
1567 dragObject.dragInfo = info;
1568 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1569 true)) {
1570 return;
1571 }
1572 } else if (touchXY != null) {
1573 // when dragging and dropping, just find the closest free spot
1574 int[] result = layout.findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, cellXY);
1575 foundCellSpan = (result != null);
1576 } else {
1577 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1578 }
1579
1580 if (!foundCellSpan) {
1581 showOutOfSpaceMessage(isHotseatLayout(layout));
1582 return;
1583 }
1584
1585 LauncherModel.addItemToDatabase(this, info, container, screenId, cellXY[0], cellXY[1], false);
1586
1587 if (!mRestoring) {
1588 mWorkspace.addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1,
1589 isWorkspaceLocked());
1590 }
1591 }
1592
1593 static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
1594 int minHeight) {
1595 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
1596 // We want to account for the extra amount of padding that we are adding to the widget
1597 // to ensure that it gets the full amount of space that it has requested
1598 int requiredWidth = minWidth + padding.left + padding.right;
1599 int requiredHeight = minHeight + padding.top + padding.bottom;
1600 return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
1601 }
1602
1603 static int[] getSpanForWidget(Context context, AppWidgetProviderInfo info) {
1604 return getSpanForWidget(context, info.provider, info.minWidth, info.minHeight);
1605 }
1606
1607 static int[] getMinSpanForWidget(Context context, AppWidgetProviderInfo info) {
1608 return getSpanForWidget(context, info.provider, info.minResizeWidth, info.minResizeHeight);
1609 }
1610
1611 static int[] getSpanForWidget(Context context, PendingAddWidgetInfo info) {
1612 return getSpanForWidget(context, info.componentName, info.minWidth, info.minHeight);
1613 }
1614
1615 static int[] getMinSpanForWidget(Context context, PendingAddWidgetInfo info) {
1616 return getSpanForWidget(context, info.componentName, info.minResizeWidth,
1617 info.minResizeHeight);
1618 }
1619
1620 /**
1621 * Add a widget to the workspace.
1622 *
1623 * @param appWidgetId The app widget id
1624 * @param cellInfo The position on screen where to create the widget.
1625 */
1626 private void completeAddAppWidget(final int appWidgetId, long container, long screenId,
1627 AppWidgetHostView hostView, AppWidgetProviderInfo appWidgetInfo) {
1628 if (appWidgetInfo == null) {
1629 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1630 }
1631
1632 // Calculate the grid spans needed to fit this widget
1633 CellLayout layout = getCellLayout(container, screenId);
1634
1635 int[] minSpanXY = getMinSpanForWidget(this, appWidgetInfo);
1636 int[] spanXY = getSpanForWidget(this, appWidgetInfo);
1637
1638 // Try finding open space on Launcher screen
1639 // We have saved the position to which the widget was dragged-- this really only matters
1640 // if we are placing widgets on a "spring-loaded" screen
1641 int[] cellXY = mTmpAddItemCellCoordinates;
1642 int[] touchXY = mPendingAddInfo.dropPos;
1643 int[] finalSpan = new int[2];
1644 boolean foundCellSpan = false;
1645 if (mPendingAddInfo.cellX >= 0 && mPendingAddInfo.cellY >= 0) {
1646 cellXY[0] = mPendingAddInfo.cellX;
1647 cellXY[1] = mPendingAddInfo.cellY;
1648 spanXY[0] = mPendingAddInfo.spanX;
1649 spanXY[1] = mPendingAddInfo.spanY;
1650 foundCellSpan = true;
1651 } else if (touchXY != null) {
1652 // when dragging and dropping, just find the closest free spot
1653 int[] result = layout.findNearestVacantArea(
1654 touchXY[0], touchXY[1], minSpanXY[0], minSpanXY[1], spanXY[0],
1655 spanXY[1], cellXY, finalSpan);
1656 spanXY[0] = finalSpan[0];
1657 spanXY[1] = finalSpan[1];
1658 foundCellSpan = (result != null);
1659 } else {
1660 foundCellSpan = layout.findCellForSpan(cellXY, minSpanXY[0], minSpanXY[1]);
1661 }
1662
1663 if (!foundCellSpan) {
1664 if (appWidgetId != -1) {
1665 // Deleting an app widget ID is a void call but writes to disk before returning
1666 // to the caller...
1667 new AsyncTask<Void, Void, Void>() {
1668 public Void doInBackground(Void ... args) {
1669 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
1670 return null;
1671 }
1672 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1673 }
1674 showOutOfSpaceMessage(isHotseatLayout(layout));
1675 return;
1676 }
1677
1678 // Build Launcher-specific widget info and save to database
1679 LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId,
1680 appWidgetInfo.provider);
1681 launcherInfo.spanX = spanXY[0];
1682 launcherInfo.spanY = spanXY[1];
1683 launcherInfo.minSpanX = mPendingAddInfo.minSpanX;
1684 launcherInfo.minSpanY = mPendingAddInfo.minSpanY;
1685 launcherInfo.user = mAppWidgetManager.getUser(appWidgetInfo);
1686
1687 LauncherModel.addItemToDatabase(this, launcherInfo,
1688 container, screenId, cellXY[0], cellXY[1], false);
1689
1690 if (!mRestoring) {
1691 if (hostView == null) {
1692 // Perform actual inflation because we're live
1693 launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1694 launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
1695 } else {
1696 // The AppWidgetHostView has already been inflated and instantiated
1697 launcherInfo.hostView = hostView;
1698 }
1699
1700 launcherInfo.hostView.setTag(launcherInfo);
1701 launcherInfo.hostView.setVisibility(View.VISIBLE);
1702 launcherInfo.notifyWidgetSizeChanged(this);
1703
1704 mWorkspace.addInScreen(launcherInfo.hostView, container, screenId, cellXY[0], cellXY[1],
1705 launcherInfo.spanX, launcherInfo.spanY, isWorkspaceLocked());
1706
1707 addWidgetToAutoAdvanceIfNeeded(launcherInfo.hostView, appWidgetInfo);
1708 }
1709 resetAddInfo();
1710 }
1711
1712 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
1713 @Override
1714 public void onReceive(Context context, Intent intent) {
1715 final String action = intent.getAction();
1716 if (Intent.ACTION_SCREEN_OFF.equals(action)) {
1717 mUserPresent = false;
1718 mDragLayer.clearAllResizeFrames();
1719 updateRunning();
1720 // Reset AllApps to its initial state only if we are not in the middle of
1721 // processing a multi-step drop
1722 if ((mAppsCustomizeTabHost != null) && (mPendingAddInfo.container == ItemInfo.NO_ID)) {
1723 showWorkspace(false);
1724 }
1725 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
1726 mUserPresent = true;
1727 updateRunning();
1728 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) {
1729 mModel.resetLoadedState(false, true);
1730 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR🔵
1731 } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) {
1732 mModel.resetLoadedState(false, true);
1733 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, LauncherModel.LOADER_FLAG_CLEAR🔵
1734 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) || LauncherAppsComp🔵
1735 getModel().forceReload();
1736 }
1737 }
1738 };
1739
1740 @Override
1741 public void onAttachedToWindow() {
1742 super.onAttachedToWindow();
1743
1744 // Listen for broadcasts related to user-presence
1745 final IntentFilter filter = new IntentFilter();
1746 filter.addAction(Intent.ACTION_SCREEN_OFF);
1747 filter.addAction(Intent.ACTION_USER_PRESENT);
1748 // For handling managed profiles
1749 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
1750 filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
1751 if (ENABLE_DEBUG_INTENTS) {
1752 filter.addAction(DebugIntents.DELETE_DATABASE);
1753 filter.addAction(DebugIntents.MIGRATE_DATABASE);
1754 }
1755 registerReceiver(mReceiver, filter);
1756 FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
1757 setupTransparentSystemBarsForLmp();
1758 mAttached = true;
1759 mVisible = true;
1760 }
1761
1762 /**
1763 * Sets up transparent navigation and status bars in LMP.
1764 * This method is a no-op for other platform versions.
1765 */
1766 @TargetApi(19)
1767 private void setupTransparentSystemBarsForLmp() {
1768 // TODO(sansid): use the APIs directly when compiling against L sdk.
1769 // Currently we use reflection to access the flags and the API to set the transparency
1770 // on the System bars.
1771 if (Utilities.isLmpOrAbove()) {
1772 try {
1773 getWindow().getAttributes().systemUiVisibility |=
1774 (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1775 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1776 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
1777 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
1778 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1779 Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField(
1780 "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS");
1781 getWindow().addFlags(drawsSysBackgroundsField.getInt(null));
1782
1783 Method setStatusBarColorMethod =
1784 Window.class.getDeclaredMethod("setStatusBarColor", int.class);
1785 Method setNavigationBarColorMethod =
1786 Window.class.getDeclaredMethod("setNavigationBarColor", int.class);
1787 setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
1788 setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
1789 } catch (NoSuchFieldException e) {
1790 Log.w(TAG, "NoSuchFieldException while setting up transparent bars");
1791 } catch (NoSuchMethodException ex) {
1792 Log.w(TAG, "NoSuchMethodException while setting up transparent bars");
1793 } catch (IllegalAccessException e) {
1794 Log.w(TAG, "IllegalAccessException while setting up transparent bars");
1795 } catch (IllegalArgumentException e) {
1796 Log.w(TAG, "IllegalArgumentException while setting up transparent bars");
1797 } catch (InvocationTargetException e) {
1798 Log.w(TAG, "InvocationTargetException while setting up transparent bars");
1799 } finally {}
1800 }
1801 }
1802
1803 @Override
1804 public void onDetachedFromWindow() {
1805 super.onDetachedFromWindow();
1806 mVisible = false;
1807
1808 if (mAttached) {
1809 unregisterReceiver(mReceiver);
1810 mAttached = false;
1811 }
1812 updateRunning();
1813 }
1814
1815 public void onWindowVisibilityChanged(int visibility) {
1816 mVisible = visibility == View.VISIBLE;
1817 updateRunning();
1818 // The following code used to be in onResume, but it turns out onResume is called when
1819 // you're in All Apps and click home to go to the workspace. onWindowVisibilityChanged
1820 // is a more appropriate event to handle
1821 if (mVisible) {
1822 mAppsCustomizeTabHost.onWindowVisible();
1823 if (!mWorkspaceLoading) {
1824 final ViewTreeObserver observer = mWorkspace.getViewTreeObserver();
1825 // We want to let Launcher draw itself at least once before we force it to build
1826 // layers on all the workspace pages, so that transitioning to Launcher from other
1827 // apps is nice and speedy.
1828 observer.addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
1829 private boolean mStarted = false;
1830 public void onDraw() {
1831 if (mStarted) return;
1832 mStarted = true;
1833 // We delay the layer building a bit in order to give
1834 // other message processing a time to run. In particular
1835 // this avoids a delay in hiding the IME if it was
1836 // currently shown, because doing that may involve
1837 // some communication back with the app.
1838 mWorkspace.postDelayed(mBuildLayersRunnable, 500);
1839 final ViewTreeObserver.OnDrawListener listener = this;
1840 mWorkspace.post(new Runnable() {
1841 public void run() {
1842 if (mWorkspace != null &&
1843 mWorkspace.getViewTreeObserver() != null) {
1844 mWorkspace.getViewTreeObserver().
1845 removeOnDrawListener(listener);
1846 }
1847 }
1848 });
1849 return;
1850 }
1851 });
1852 }
1853 clearTypedText();
1854 }
1855 }
1856
1857 private void sendAdvanceMessage(long delay) {
1858 mHandler.removeMessages(ADVANCE_MSG);
1859 Message msg = mHandler.obtainMessage(ADVANCE_MSG);
1860 mHandler.sendMessageDelayed(msg, delay);
1861 mAutoAdvanceSentTime = System.currentTimeMillis();
1862 }
1863
1864 private void updateRunning() {
1865 boolean autoAdvanceRunning = mVisible && mUserPresent && !mWidgetsToAdvance.isEmpty();
1866 if (autoAdvanceRunning != mAutoAdvanceRunning) {
1867 mAutoAdvanceRunning = autoAdvanceRunning;
1868 if (autoAdvanceRunning) {
1869 long delay = mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
1870 sendAdvanceMessage(delay);
1871 } else {
1872 if (!mWidgetsToAdvance.isEmpty()) {
1873 mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
1874 (System.currentTimeMillis() - mAutoAdvanceSentTime));
1875 }
1876 mHandler.removeMessages(ADVANCE_MSG);
1877 mHandler.removeMessages(0); // Remove messages sent using postDelayed()
1878 }
1879 }
1880 }
1881
1882 private final Handler mHandler = new Handler() {
1883 @Override
1884 public void handleMessage(Message msg) {
1885 if (msg.what == ADVANCE_MSG) {
1886 int i = 0;
1887 for (View key: mWidgetsToAdvance.keySet()) {
1888 final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
1889 final int delay = mAdvanceStagger * i;
1890 if (v instanceof Advanceable) {
1891 postDelayed(new Runnable() {
1892 public void run() {
1893 ((Advanceable) v).advance();
1894 }
1895 }, delay);
1896 }
1897 i++;
1898 }
1899 sendAdvanceMessage(mAdvanceInterval);
1900 }
1901 }
1902 };
1903
1904 void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
1905 if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
1906 View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
1907 if (v instanceof Advanceable) {
1908 mWidgetsToAdvance.put(hostView, appWidgetInfo);
1909 ((Advanceable) v).fyiWillBeAdvancedByHostKThx();
1910 updateRunning();
1911 }
1912 }
1913
1914 void removeWidgetToAutoAdvance(View hostView) {
1915 if (mWidgetsToAdvance.containsKey(hostView)) {
1916 mWidgetsToAdvance.remove(hostView);
1917 updateRunning();
1918 }
1919 }
1920
1921 public void removeAppWidget(LauncherAppWidgetInfo launcherInfo) {
1922 removeWidgetToAutoAdvance(launcherInfo.hostView);
1923 launcherInfo.hostView = null;
1924 }
1925
1926 void showOutOfSpaceMessage(boolean isHotseatLayout) {
1927 int strId = (isHotseatLayout ? R.string.hotseat_out_of_space : R.string.out_of_space);
1928 Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT).show();
1929 }
1930
1931 public DragLayer getDragLayer() {
1932 return mDragLayer;
1933 }
1934
1935 public Workspace getWorkspace() {
1936 return mWorkspace;
1937 }
1938
1939 public Hotseat getHotseat() {
1940 return mHotseat;
1941 }
1942
1943 public ViewGroup getOverviewPanel() {
1944 return mOverviewPanel;
1945 }
1946
1947 public SearchDropTargetBar getSearchBar() {
1948 return mSearchDropTargetBar;
1949 }
1950
1951 public LauncherAppWidgetHost getAppWidgetHost() {
1952 return mAppWidgetHost;
1953 }
1954
1955 public LauncherModel getModel() {
1956 return mModel;
1957 }
1958
1959 protected SharedPreferences getSharedPrefs() {
1960 return mSharedPrefs;
1961 }
1962
1963 public void closeSystemDialogs() {
1964 getWindow().closeAllPanels();
1965
1966 // Whatever we were doing is hereby canceled.
1967 setWaitingForResult(false);
1968 }
1969
1970 @Override
1971 protected void onNewIntent(Intent intent) {
1972 long startTime = 0;
1973 if (DEBUG_RESUME_TIME) {
1974 startTime = System.currentTimeMillis();
1975 }
1976 super.onNewIntent(intent);
1977
1978 // Close the menu
1979 if (Intent.ACTION_MAIN.equals(intent.getAction())) {
1980 // also will cancel mWaitingForResult.
1981 closeSystemDialogs();
1982
1983 final boolean alreadyOnHome = mHasFocus && ((intent.getFlags() &
1984 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1985 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1986
1987 if (mWorkspace == null) {
1988 // Can be cases where mWorkspace is null, this prevents a NPE
1989 return;
1990 }
1991 Folder openFolder = mWorkspace.getOpenFolder();
1992 // In all these cases, only animate if we're already on home
1993 mWorkspace.exitWidgetResizeMode();
1994 if (alreadyOnHome && mState == State.WORKSPACE && !mWorkspace.isTouchActive() &&
1995 openFolder == null && shouldMoveToDefaultScreenOnHomeIntent()) {
1996 mWorkspace.moveToDefaultScreen(true);
1997 }
1998
1999 closeFolder();
2000 exitSpringLoadedDragMode();
2001
2002 // If we are already on home, then just animate back to the workspace,
2003 // otherwise, just wait until onResume to set the state back to Workspace
2004 if (alreadyOnHome) {
2005 showWorkspace(true);
2006 } else {
2007 mOnResumeState = State.WORKSPACE;
2008 }
2009
2010 final View v = getWindow().peekDecorView();
2011 if (v != null && v.getWindowToken() != null) {
2012 InputMethodManager imm = (InputMethodManager)getSystemService(
2013 INPUT_METHOD_SERVICE);
2014 imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
2015 }
2016
2017 // Reset the apps customize page
2018 if (!alreadyOnHome && mAppsCustomizeTabHost != null) {
2019 mAppsCustomizeTabHost.reset();
2020 }
2021
2022 onHomeIntent();
2023 }
2024
2025 if (DEBUG_RESUME_TIME) {
2026 Log.d(TAG, "Time spent in onNewIntent: " + (System.currentTimeMillis() - startTime));
2027 }
2028 }
2029
2030 /**
2031 * Override point for subclasses to prevent movement to the default screen when the home
2032 * button is pressed. Used (for example) in GEL, to prevent movement during a search.
2033 */
2034 protected boolean shouldMoveToDefaultScreenOnHomeIntent() {
2035 return true;
2036 }
2037
2038 /**
2039 * Override point for subclasses to provide custom behaviour for when a home intent is fired.
2040 */
2041 protected void onHomeIntent() {
2042 // Do nothing
2043 }
2044
2045 @Override
2046 public void onRestoreInstanceState(Bundle state) {
2047 super.onRestoreInstanceState(state);
2048 for (int page: mSynchronouslyBoundPages) {
2049 mWorkspace.restoreInstanceStateForChild(page);
2050 }
2051 }
2052
2053 @Override
2054 protected void onSaveInstanceState(Bundle outState) {
2055 if (mWorkspace.getChildCount() > 0) {
2056 outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
2057 mWorkspace.getCurrentPageOffsetFromCustomContent());
2058 }
2059 super.onSaveInstanceState(outState);
2060
2061 outState.putInt(RUNTIME_STATE, mState.ordinal());
2062 // We close any open folder since it will not be re-opened, and we need to make sure
2063 // this state is reflected.
2064 closeFolder();
2065
2066 if (mPendingAddInfo.container != ItemInfo.NO_ID && mPendingAddInfo.screenId > -1 &&
2067 mWaitingForResult) {
2068 outState.putLong(RUNTIME_STATE_PENDING_ADD_CONTAINER, mPendingAddInfo.container);
2069 outState.putLong(RUNTIME_STATE_PENDING_ADD_SCREEN, mPendingAddInfo.screenId);
2070 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_X, mPendingAddInfo.cellX);
2071 outState.putInt(RUNTIME_STATE_PENDING_ADD_CELL_Y, mPendingAddInfo.cellY);
2072 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_X, mPendingAddInfo.spanX);
2073 outState.putInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y, mPendingAddInfo.spanY);
2074 outState.putParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO, mPendingAddWidgetInfo);
2075 outState.putInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID, mPendingAddWidgetId);
2076 }
2077
2078 if (mFolderInfo != null && mWaitingForResult) {
2079 outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
2080 outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
2081 }
2082
2083 // Save the current AppsCustomize tab
2084 if (mAppsCustomizeTabHost != null) {
2085 AppsCustomizePagedView.ContentType type = mAppsCustomizeContent.getContentType();
2086 String currentTabTag = mAppsCustomizeTabHost.getTabTagForContentType(type);
2087 if (currentTabTag != null) {
2088 outState.putString("apps_customize_currentTab", currentTabTag);
2089 }
2090 int currentIndex = mAppsCustomizeContent.getSaveInstanceStateIndex();
2091 outState.putInt("apps_customize_currentIndex", currentIndex);
2092 }
2093 outState.putSerializable(RUNTIME_STATE_VIEW_IDS, mItemIdToViewId);
2094 }
2095
2096 @Override
2097 public void onDestroy() {
2098 super.onDestroy();
2099
2100 // Remove all pending runnables
2101 mHandler.removeMessages(ADVANCE_MSG);
2102 mHandler.removeMessages(0);
2103 mWorkspace.removeCallbacks(mBuildLayersRunnable);
2104
2105 // Stop callbacks from LauncherModel
2106 LauncherAppState app = (LauncherAppState.getInstance());
2107
2108 // It's possible to receive onDestroy after a new Launcher activity has
2109 // been created. In this case, don't interfere with the new Launcher.
2110 if (mModel.isCurrentCallbacks(this)) {
2111 mModel.stopLoader();
2112 app.setLauncher(null);
2113 }
2114
2115 try {
2116 mAppWidgetHost.stopListening();
2117 } catch (NullPointerException ex) {
2118 Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
2119 }
2120 mAppWidgetHost = null;
2121
2122 mWidgetsToAdvance.clear();
2123
2124 TextKeyListener.getInstance().release();
2125
2126 // Disconnect any of the callbacks and drawables associated with ItemInfos on the workspace
2127 // to prevent leaking Launcher activities on orientation change.
2128 if (mModel != null) {
2129 mModel.unbindItemInfosAndClearQueuedBindRunnables();
2130 }
2131
2132 getContentResolver().unregisterContentObserver(mWidgetObserver);
2133 unregisterReceiver(mCloseSystemDialogsReceiver);
2134
2135 mDragLayer.clearAllResizeFrames();
2136 ((ViewGroup) mWorkspace.getParent()).removeAllViews();
2137 mWorkspace.removeAllWorkspaceScreens();
2138 mWorkspace = null;
2139 mDragController = null;
2140
2141 PackageInstallerCompat.getInstance(this).onStop();
2142 LauncherAnimUtils.onDestroyActivity();
2143 }
2144
2145 public DragController getDragController() {
2146 return mDragController;
2147 }
2148
2149 @Override
2150 public void startActivityForResult(Intent intent, int requestCode) {
2151 if (requestCode >= 0) {
2152 setWaitingForResult(true);
2153 }
2154 super.startActivityForResult(intent, requestCode);
2155 }
2156
2157 /**
2158 * Indicates that we want global search for this activity by setting the globalSearch
2159 * argument for {@link #startSearch} to true.
2160 */
2161 @Override
2162 public void startSearch(String initialQuery, boolean selectInitialQuery,
2163 Bundle appSearchData, boolean globalSearch) {
2164
2165 showWorkspace(true);
2166
2167 if (initialQuery == null) {
2168 // Use any text typed in the launcher as the initial query
2169 initialQuery = getTypedText();
2170 }
2171 if (appSearchData == null) {
2172 appSearchData = new Bundle();
2173 appSearchData.putString("source", "launcher-search");
2174 }
2175 Rect sourceBounds = new Rect();
2176 if (mSearchDropTargetBar != null) {
2177 sourceBounds = mSearchDropTargetBar.getSearchBarBounds();
2178 }
2179
2180 boolean clearTextImmediately = startSearch(initialQuery, selectInitialQuery,
2181 appSearchData, sourceBounds);
2182 if (clearTextImmediately) {
2183 clearTypedText();
2184 }
2185 }
2186
2187 /**
2188 * Start a text search.
2189 *
2190 * @return {@code true} if the search will start immediately, so any further keypresses
2191 * will be handled directly by the search UI. {@code false} if {@link Launcher} should continue
2192 * to buffer keypresses.
2193 */
2194 public boolean startSearch(String initialQuery,
2195 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2196 startGlobalSearch(initialQuery, selectInitialQuery,
2197 appSearchData, sourceBounds);
2198 return false;
2199 }
2200
2201 /**
2202 * Starts the global search activity. This code is a copied from SearchManager
2203 */
2204 private void startGlobalSearch(String initialQuery,
2205 boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds) {
2206 final SearchManager searchManager =
2207 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2208 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
2209 if (globalSearchActivity == null) {
2210 Log.w(TAG, "No global search activity found.");
2211 return;
2212 }
2213 Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
2214 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2215 intent.setComponent(globalSearchActivity);
2216 // Make sure that we have a Bundle to put source in
2217 if (appSearchData == null) {
2218 appSearchData = new Bundle();
2219 } else {
2220 appSearchData = new Bundle(appSearchData);
2221 }
2222 // Set source to package name of app that starts global search, if not set already.
2223 if (!appSearchData.containsKey("source")) {
2224 appSearchData.putString("source", getPackageName());
2225 }
2226 intent.putExtra(SearchManager.APP_DATA, appSearchData);
2227 if (!TextUtils.isEmpty(initialQuery)) {
2228 intent.putExtra(SearchManager.QUERY, initialQuery);
2229 }
2230 if (selectInitialQuery) {
2231 intent.putExtra(SearchManager.EXTRA_SELECT_QUERY, selectInitialQuery);
2232 }
2233 intent.setSourceBounds(sourceBounds);
2234 try {
2235 startActivity(intent);
2236 } catch (ActivityNotFoundException ex) {
2237 Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
2238 }
2239 }
2240
2241 public boolean isOnCustomContent() {
2242 return mWorkspace.isOnOrMovingToCustomContent();
2243 }
2244
2245 @Override
2246 public boolean onPrepareOptionsMenu(Menu menu) {
2247 super.onPrepareOptionsMenu(menu);
2248 if (!isOnCustomContent()) {
2249 // Close any open folders
2250 closeFolder();
2251 // Stop resizing any widgets
2252 mWorkspace.exitWidgetResizeMode();
2253 if (!mWorkspace.isInOverviewMode()) {
2254 // Show the overview mode
2255 showOverviewMode(true);
2256 } else {
2257 showWorkspace(true);
2258 }
2259 }
2260 return false;
2261 }
2262
2263 @Override
2264 public boolean onSearchRequested() {
2265 startSearch(null, false, null, true);
2266 // Use a custom animation for launching search
2267 return true;
2268 }
2269
2270 public boolean isWorkspaceLocked() {
2271 return mWorkspaceLoading || mWaitingForResult;
2272 }
2273
2274 public boolean isWorkspaceLoading() {
2275 return mWorkspaceLoading;
2276 }
2277
2278 private void setWorkspaceLoading(boolean value) {
2279 boolean isLocked = isWorkspaceLocked();
2280 mWorkspaceLoading = value;
2281 if (isLocked != isWorkspaceLocked()) {
2282 onWorkspaceLockedChanged();
2283 }
2284 }
2285
2286 private void setWaitingForResult(boolean value) {
2287 boolean isLocked = isWorkspaceLocked();
2288 mWaitingForResult = value;
2289 if (isLocked != isWorkspaceLocked()) {
2290 onWorkspaceLockedChanged();
2291 }
2292 }
2293
2294 protected void onWorkspaceLockedChanged() { }
2295
2296 private void resetAddInfo() {
2297 mPendingAddInfo.container = ItemInfo.NO_ID;
2298 mPendingAddInfo.screenId = -1;
2299 mPendingAddInfo.cellX = mPendingAddInfo.cellY = -1;
2300 mPendingAddInfo.spanX = mPendingAddInfo.spanY = -1;
2301 mPendingAddInfo.minSpanX = mPendingAddInfo.minSpanY = -1;
2302 mPendingAddInfo.dropPos = null;
2303 }
2304
2305 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2306 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo) {
2307 addAppWidgetImpl(appWidgetId, info, boundWidget, appWidgetInfo, 0);
2308 }
2309
2310 void addAppWidgetImpl(final int appWidgetId, final ItemInfo info,
2311 final AppWidgetHostView boundWidget, final AppWidgetProviderInfo appWidgetInfo, int
2312 delay) {
2313 if (appWidgetInfo.configure != null) {
2314 mPendingAddWidgetInfo = appWidgetInfo;
2315 mPendingAddWidgetId = appWidgetId;
2316
2317 // Launch over to configure widget, if needed
2318 mAppWidgetManager.startConfigActivity(appWidgetInfo, appWidgetId, this,
2319 mAppWidgetHost, REQUEST_CREATE_APPWIDGET);
2320
2321 } else {
2322 // Otherwise just add it
2323 Runnable onComplete = new Runnable() {
2324 @Override
2325 public void run() {
2326 // Exit spring loaded mode if necessary after adding the widget
2327 exitSpringLoadedDragModeDelayed(true, EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
2328 null);
2329 }
2330 };
2331 completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
2332 appWidgetInfo);
2333 mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
2334 }
2335 }
2336
2337 protected void moveToCustomContentScreen(boolean animate) {
2338 // Close any folders that may be open.
2339 closeFolder();
2340 mWorkspace.moveToCustomContentScreen(animate);
2341 }
2342
2343 /**
2344 * Process a shortcut drop.
2345 *
2346 * @param componentName The name of the component
2347 * @param screenId The ID of the screen where it should be added
2348 * @param cell The cell it should be added to, optional
2349 * @param position The location on the screen where it was dropped, optional
2350 */
2351 void processShortcutFromDrop(ComponentName componentName, long container, long screenId,
2352 int[] cell, int[] loc) {
2353 resetAddInfo();
2354 mPendingAddInfo.container = container;
2355 mPendingAddInfo.screenId = screenId;
2356 mPendingAddInfo.dropPos = loc;
2357
2358 if (cell != null) {
2359 mPendingAddInfo.cellX = cell[0];
2360 mPendingAddInfo.cellY = cell[1];
2361 }
2362
2363 Intent createShortcutIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
2364 createShortcutIntent.setComponent(componentName);
2365 processShortcut(createShortcutIntent);
2366 }
2367
2368 /**
2369 * Process a widget drop.
2370 *
2371 * @param info The PendingAppWidgetInfo of the widget being added.
2372 * @param screenId The ID of the screen where it should be added
2373 * @param cell The cell it should be added to, optional
2374 * @param position The location on the screen where it was dropped, optional
2375 */
2376 void addAppWidgetFromDrop(PendingAddWidgetInfo info, long container, long screenId,
2377 int[] cell, int[] span, int[] loc) {
2378 resetAddInfo();
2379 mPendingAddInfo.container = info.container = container;
2380 mPendingAddInfo.screenId = info.screenId = screenId;
2381 mPendingAddInfo.dropPos = loc;
2382 mPendingAddInfo.minSpanX = info.minSpanX;
2383 mPendingAddInfo.minSpanY = info.minSpanY;
2384
2385 if (cell != null) {
2386 mPendingAddInfo.cellX = cell[0];
2387 mPendingAddInfo.cellY = cell[1];
2388 }
2389 if (span != null) {
2390 mPendingAddInfo.spanX = span[0];
2391 mPendingAddInfo.spanY = span[1];
2392 }
2393
2394 AppWidgetHostView hostView = info.boundWidget;
2395 int appWidgetId;
2396 if (hostView != null) {
2397 appWidgetId = hostView.getAppWidgetId();
2398 addAppWidgetImpl(appWidgetId, info, hostView, info.info);
2399 } else {
2400 // In this case, we either need to start an activity to get permission to bind
2401 // the widget, or we need to start an activity to configure the widget, or both.
2402 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
2403 Bundle options = info.bindOptions;
2404
2405 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2406 appWidgetId, info.info, options);
2407 if (success) {
2408 addAppWidgetImpl(appWidgetId, info, null, info.info);
2409 } else {
2410 mPendingAddWidgetInfo = info.info;
2411 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
2412 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
2413 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
2414 mAppWidgetManager.getUser(mPendingAddWidgetInfo)
2415 .addToIntent(intent, AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE);
2416 // TODO: we need to make sure that this accounts for the options bundle.
2417 // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
2418 startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
2419 }
2420 }
2421 }
2422
2423 void processShortcut(Intent intent) {
2424 Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
2425 }
2426
2427 void processWallpaper(Intent intent) {
2428 startActivityForResult(intent, REQUEST_PICK_WALLPAPER);
2429 }
2430
2431 FolderIcon addFolder(CellLayout layout, long container, final long screenId, int cellX,
2432 int cellY) {
2433 final FolderInfo folderInfo = new FolderInfo();
2434 folderInfo.title = getText(R.string.folder_name);
2435
2436 // Update the model
2437 LauncherModel.addItemToDatabase(Launcher.this, folderInfo, container, screenId, cellX, cellY,
2438 false);
2439 sFolders.put(folderInfo.id, folderInfo);
2440
2441 // Create the view
2442 FolderIcon newFolder =
2443 FolderIcon.fromXml(R.layout.folder_icon, this, layout, folderInfo, mIconCache);
2444 mWorkspace.addInScreen(newFolder, container, screenId, cellX, cellY, 1, 1,
2445 isWorkspaceLocked());
2446 // Force measure the new folder icon
2447 CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
2448 parent.getShortcutsAndWidgets().measureChild(newFolder);
2449 return newFolder;
2450 }
2451
2452 void removeFolder(FolderInfo folder) {
2453 sFolders.remove(folder.id);
2454 }
2455
2456 protected ComponentName getWallpaperPickerComponent() {
2457 return new ComponentName(getPackageName(), LauncherWallpaperPickerActivity.class.getName());
2458 }
2459
2460 /**
2461 * Registers various content observers. The current implementation registers
2462 * only a favorites observer to keep track of the favorites applications.
2463 */
2464 private void registerContentObservers() {
2465 ContentResolver resolver = getContentResolver();
2466 resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
2467 true, mWidgetObserver);
2468 }
2469
2470 @Override
2471 public boolean dispatchKeyEvent(KeyEvent event) {
2472 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2473 switch (event.getKeyCode()) {
2474 case KeyEvent.KEYCODE_HOME:
2475 return true;
2476 case KeyEvent.KEYCODE_VOLUME_DOWN:
2477 if (isPropertyEnabled(DUMP_STATE_PROPERTY)) {
2478 dumpState();
2479 return true;
2480 }
2481 break;
2482 }
2483 } else if (event.getAction() == KeyEvent.ACTION_UP) {
2484 switch (event.getKeyCode()) {
2485 case KeyEvent.KEYCODE_HOME:
2486 return true;
2487 }
2488 }
2489
2490 return super.dispatchKeyEvent(event);
2491 }
2492
2493 @Override
2494 public void onBackPressed() {
2495 if (isAllAppsVisible()) {
2496 if (mAppsCustomizeContent.getContentType() ==
2497 AppsCustomizePagedView.ContentType.Applications) {
2498 showWorkspace(true);
2499 } else {
2500 showOverviewMode(true);
2501 }
2502 } else if (mWorkspace.isInOverviewMode()) {
2503 mWorkspace.exitOverviewMode(true);
2504 } else if (mWorkspace.getOpenFolder() != null) {
2505 Folder openFolder = mWorkspace.getOpenFolder();
2506 if (openFolder.isEditingName()) {
2507 openFolder.dismissEditingName();
2508 } else {
2509 closeFolder();
2510 }
2511 } else {
2512 mWorkspace.exitWidgetResizeMode();
2513
2514 // Back button is a no-op here, but give at least some feedback for the button press
2515 mWorkspace.showOutlinesTemporarily();
2516 }
2517 }
2518
2519 /**
2520 * Re-listen when widgets are reset.
2521 */
2522 private void onAppWidgetReset() {
2523 if (mAppWidgetHost != null) {
2524 mAppWidgetHost.startListening();
2525 }
2526 }
2527
2528 /**
2529 * Launches the intent referred by the clicked shortcut.
2530 *
2531 * @param v The view representing the clicked shortcut.
2532 */
2533 public void onClick(View v) {
2534 // Make sure that rogue clicks don't get through while allapps is launching, or after the
2535 // view has detached (it's possible for this to happen if the view is removed mid touch).
2536 if (v.getWindowToken() == null) {
2537 return;
2538 }
2539
2540 if (!mWorkspace.isFinishedSwitchingState()) {
2541 return;
2542 }
2543
2544 if (v instanceof Workspace) {
2545 if (mWorkspace.isInOverviewMode()) {
2546 mWorkspace.exitOverviewMode(true);
2547 }
2548 return;
2549 }
2550
2551 if (v instanceof CellLayout) {
2552 if (mWorkspace.isInOverviewMode()) {
2553 mWorkspace.exitOverviewMode(mWorkspace.indexOfChild(v), true);
2554 }
2555 }
2556
2557 Object tag = v.getTag();
2558 if (tag instanceof ShortcutInfo) {
2559 onClickAppShortcut(v);
2560 } else if (tag instanceof FolderInfo) {
2561 if (v instanceof FolderIcon) {
2562 onClickFolderIcon(v);
2563 }
2564 } else if (v == mAllAppsButton) {
2565 onClickAllAppsButton(v);
2566 } else if (tag instanceof AppInfo) {
2567 startAppShortcutOrInfoActivity(v);
2568 } else if (tag instanceof LauncherAppWidgetInfo) {
2569 if (v instanceof PendingAppWidgetHostView) {
2570 onClickPendingWidget((PendingAppWidgetHostView) v);
2571 }
2572 }
2573 }
2574
2575 public void onClickPagedViewIcon(View v) {
2576 startAppShortcutOrInfoActivity(v);
2577 }
2578
2579 public boolean onTouch(View v, MotionEvent event) {
2580 return false;
2581 }
2582
2583 /**
2584 * Event handler for the app widget view which has not fully restored.
2585 */
2586 public void onClickPendingWidget(final PendingAppWidgetHostView v) {
2587 final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
2588 if (v.isReadyForClickSetup()) {
2589 int widgetId = info.appWidgetId;
2590 AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId);
2591 if (appWidgetInfo != null) {
2592 mPendingAddWidgetInfo = appWidgetInfo;
2593 mPendingAddInfo.copyFrom(info);
2594 mPendingAddWidgetId = widgetId;
2595
2596 AppWidgetManagerCompat.getInstance(this).startConfigActivity(appWidgetInfo,
2597 info.appWidgetId, this, mAppWidgetHost, REQUEST_RECONFIGURE_APPWIDGET);
2598 }
2599 } else if (info.installProgress < 0) {
2600 // The install has not been queued
2601 final String packageName = info.providerName.getPackageName();
2602 showBrokenAppInstallDialog(packageName,
2603 new DialogInterface.OnClickListener() {
2604 public void onClick(DialogInterface dialog, int id) {
2605 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2606 }
2607 });
2608 } else {
2609 // Download has started.
2610 final String packageName = info.providerName.getPackageName();
2611 startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info);
2612 }
2613 }
2614
2615 /**
2616 * Event handler for the search button
2617 *
2618 * @param v The view that was clicked.
2619 */
2620 public void onClickSearchButton(View v) {
2621 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2622
2623 onSearchRequested();
2624 }
2625
2626 /**
2627 * Event handler for the voice button
2628 *
2629 * @param v The view that was clicked.
2630 */
2631 public void onClickVoiceButton(View v) {
2632 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2633
2634 startVoice();
2635 }
2636
2637 public void startVoice() {
2638 try {
2639 final SearchManager searchManager =
2640 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
2641 ComponentName activityName = searchManager.getGlobalSearchActivity();
2642 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2643 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2644 if (activityName != null) {
2645 intent.setPackage(activityName.getPackageName());
2646 }
2647 startActivity(null, intent, "onClickVoiceButton");
2648 } catch (ActivityNotFoundException e) {
2649 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
2650 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2651 startActivitySafely(null, intent, "onClickVoiceButton");
2652 }
2653 }
2654
2655 /**
2656 * Event handler for the "grid" button that appears on the home screen, which
2657 * enters all apps mode.
2658 *
2659 * @param v The view that was clicked.
2660 */
2661 protected void onClickAllAppsButton(View v) {
2662 if (LOGD) Log.d(TAG, "onClickAllAppsButton");
2663 if (isAllAppsVisible()) {
2664 showWorkspace(true);
2665 } else {
2666 showAllApps(true, AppsCustomizePagedView.ContentType.Applications, false);
2667 }
2668 }
2669
2670 private void showBrokenAppInstallDialog(final String packageName, DialogInterface.OnClickListener onS🔵
2671 new AlertDialog.Builder(this).setTitle(R.string.abandoned_promises_title).setMessage(R.string.aba🔵
2672 public void onClick(DialogInterface dialog, int id) {
2673 final UserHandleCompat user = UserHandleCompat.myUserHandle();
2674 mWorkspace.removeAbandonedPromise(packageName, user);
2675 }
2676 }).create().show();
2677 return;
2678 }
2679
2680 /**
2681 * Event handler for an app shortcut click.
2682 *
2683 * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
2684 */
2685 protected void onClickAppShortcut(final View v) {
2686 if (LOGD) {
2687 Log.d(TAG, "onClickAppShortcut");
2688 }
2689 Object tag = v.getTag();
2690 if (!(tag instanceof ShortcutInfo)) {
2691 throw new IllegalArgumentException("Input must be a Shortcut");
2692 }
2693 // Open shortcut
2694 final ShortcutInfo shortcut = ((ShortcutInfo) (tag));
2695 if (shortcut.isDisabled != 0) {
2696 int error = R.string.activity_not_available;
2697 if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
2698 error = R.string.safemode_shortcut_error;
2699 }
2700 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
2701 return;
2702 }
2703 final Intent intent = shortcut.intent;
2704 // Check for special shortcuts
2705 if (intent.getComponent() != null) {
2706 final String shortcutClass = intent.getComponent().getClassName();
2707 if (shortcutClass.equals(MemoryDumpActivity.class.getName())) {
2708 MemoryDumpActivity.startDump(this);
2709 return;
2710 } else if (shortcutClass.equals(ToggleWeightWatcher.class.getName())) {
2711 toggleShowWeightWatcher();
2712 return;
2713 }
2714 }
2715 // Check for abandoned promise
2716 if (((v instanceof BubbleTextView) && shortcut.isPromise()) && (!shortcut.hasStatusFlag(ShortcutI🔵
2717 showBrokenAppInstallDialog(shortcut.getTargetComponent().getPackageName(), new DialogInterfac🔵
2718 public void onClick(DialogInterface dialog, int id) {
2719 startAppShortcutOrInfoActivity(v);
2720 }
2721 });
2722 return;
2723 }
2724 // Start activities
2725 startAppShortcutOrInfoActivity(v);
2726 }
2727
2728 private void startAppShortcutOrInfoActivity(View v) {
2729 Object tag = v.getTag();
2730 final ShortcutInfo shortcut;
2731 final Intent intent;
2732 if (tag instanceof ShortcutInfo) {
2733 shortcut = (ShortcutInfo) tag;
2734 intent = shortcut.intent;
2735 int[] pos = new int[2];
2736 v.getLocationOnScreen(pos);
2737 intent.setSourceBounds(new Rect(pos[0], pos[1],
2738 pos[0] + v.getWidth(), pos[1] + v.getHeight()));
2739
2740 } else if (tag instanceof AppInfo) {
2741 shortcut = null;
2742 intent = ((AppInfo) tag).intent;
2743 } else {
2744 throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
2745 }
2746
2747 boolean success = startActivitySafely(v, intent, tag);
2748 mStats.recordLaunch(intent, shortcut);
2749
2750 if (success && v instanceof BubbleTextView) {
2751 mWaitingForResume = (BubbleTextView) v;
2752 mWaitingForResume.setStayPressed(true);
2753 }
2754 }
2755
2756 /**
2757 * Event handler for a folder icon click.
2758 *
2759 * @param v The view that was clicked. Must be an instance of {@link FolderIcon}.
2760 */
2761 protected void onClickFolderIcon(View v) {
2762 if (LOGD) Log.d(TAG, "onClickFolder");
2763 if (!(v instanceof FolderIcon)){
2764 throw new IllegalArgumentException("Input must be a FolderIcon");
2765 }
2766
2767 FolderIcon folderIcon = (FolderIcon) v;
2768 final FolderInfo info = folderIcon.getFolderInfo();
2769 Folder openFolder = mWorkspace.getFolderForTag(info);
2770
2771 // If the folder info reports that the associated folder is open, then verify that
2772 // it is actually opened. There have been a few instances where this gets out of sync.
2773 if (info.opened && openFolder == null) {
2774 Log.d(TAG, "Folder info marked as open, but associated folder is not open. Screen: "
2775 + info.screenId + " (" + info.cellX + ", " + info.cellY + ")");
2776 info.opened = false;
2777 }
2778
2779 if (!info.opened && !folderIcon.getFolder().isDestroyed()) {
2780 // Close any open folder
2781 closeFolder();
2782 // Open the requested folder
2783 openFolder(folderIcon);
2784 } else {
2785 // Find the open folder...
2786 int folderScreen;
2787 if (openFolder != null) {
2788 folderScreen = mWorkspace.getPageForView(openFolder);
2789 // .. and close it
2790 closeFolder(openFolder);
2791 if (folderScreen != mWorkspace.getCurrentPage()) {
2792 // Close any folder open on the current screen
2793 closeFolder();
2794 // Pull the folder onto this screen
2795 openFolder(folderIcon);
2796 }
2797 }
2798 }
2799 }
2800
2801 /**
2802 * Event handler for the (Add) Widgets button that appears after a long press
2803 * on the home screen.
2804 */
2805 protected void onClickAddWidgetButton(View view) {
2806 if (LOGD) Log.d(TAG, "onClickAddWidgetButton");
2807 showAllApps(true, AppsCustomizePagedView.ContentType.Widgets, true);
2808 }
2809
2810 /**
2811 * Event handler for the wallpaper picker button that appears after a long press
2812 * on the home screen.
2813 */
2814 protected void onClickWallpaperPicker(View v) {
2815 if (LOGD) Log.d(TAG, "onClickWallpaperPicker");
2816 final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER);
2817 pickWallpaper.setComponent(getWallpaperPickerComponent());
2818 startActivityForResult(pickWallpaper, REQUEST_PICK_WALLPAPER);
2819 }
2820
2821 /**
2822 * Event handler for a click on the settings button that appears after a long press
2823 * on the home screen.
2824 */
2825 protected void onClickSettingsButton(View v) {
2826 if (LOGD) Log.d(TAG, "onClickSettingsButton");
2827 }
2828
2829 public void onTouchDownAllAppsButton(View v) {
2830 // Provide the same haptic feedback that the system offers for virtual keys.
2831 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2832 }
2833
2834 public void performHapticFeedbackOnTouchDown(View v) {
2835 // Provide the same haptic feedback that the system offers for virtual keys.
2836 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2837 }
2838
2839 public View.OnTouchListener getHapticFeedbackTouchListener() {
2840 if (mHapticFeedbackTouchListener == null) {
2841 mHapticFeedbackTouchListener = new View.OnTouchListener() {
2842 @Override
2843 public boolean onTouch(View v, MotionEvent event) {
2844 if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
2845 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
2846 }
2847 return false;
2848 }
2849 };
2850 }
2851 return mHapticFeedbackTouchListener;
2852 }
2853
2854 public void onDragStarted(View view) {}
2855
2856 /**
2857 * Called when the user stops interacting with the launcher.
2858 * This implies that the user is now on the homescreen and is not doing housekeeping.
2859 */
2860 protected void onInteractionEnd() {}
2861
2862 /**
2863 * Called when the user starts interacting with the launcher.
2864 * The possible interactions are:
2865 * - open all apps
2866 * - reorder an app shortcut, or a widget
2867 * - open the overview mode.
2868 * This is a good time to stop doing things that only make sense
2869 * when the user is on the homescreen and not doing housekeeping.
2870 */
2871 protected void onInteractionBegin() {}
2872
2873 void startApplicationDetailsActivity(ComponentName componentName, UserHandleCompat user) {
2874 String packageName = componentName.getPackageName();
2875 try {
2876 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2877 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2878 launcherApps.showAppDetailsForProfile(componentName, user);
2879 } catch (SecurityException e) {
2880 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2881 Log.e(TAG, "Launcher does not have permission to launch settings");
2882 } catch (ActivityNotFoundException e) {
2883 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2884 Log.e(TAG, "Unable to launch settings");
2885 }
2886 }
2887
2888 // returns true if the activity was started
2889 boolean startApplicationUninstallActivity(ComponentName componentName, int flags,
2890 UserHandleCompat user) {
2891 if ((flags & AppInfo.DOWNLOADED_FLAG) == 0) {
2892 // System applications cannot be installed. For now, show a toast explaining that.
2893 // We may give them the option of disabling apps this way.
2894 int messageId = R.string.uninstall_system_app_text;
2895 Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
2896 return false;
2897 } else {
2898 String packageName = componentName.getPackageName();
2899 String className = componentName.getClassName();
2900 Intent intent = new Intent(
2901 Intent.ACTION_DELETE, Uri.fromParts("package", packageName, className));
2902 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2903 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2904 if (user != null) {
2905 user.addToIntent(intent, Intent.EXTRA_USER);
2906 }
2907 startActivity(intent);
2908 return true;
2909 }
2910 }
2911
2912 boolean startActivity(View v, Intent intent, Object tag) {
2913 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2914 try {
2915 // Only launch using the new animation if the shortcut has not opted out (this is a
2916 // private contract between launcher and may be ignored in the future).
2917 boolean useLaunchAnimation = (v != null) &&
2918 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
2919 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
2920 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
2921
2922 UserHandleCompat user = null;
2923 if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
2924 long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
2925 user = userManager.getUserForSerialNumber(serialNumber);
2926 }
2927
2928 Bundle optsBundle = null;
2929 if (useLaunchAnimation) {
2930 ActivityOptions opts = Utilities.isLmpOrAbove() ?
2931 ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim)🔵
2932 ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getMeasuredWidth(), v.getMeasured🔵
2933 optsBundle = opts.toBundle();
2934 }
2935
2936 if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
2937 // Could be launching some bookkeeping activity
2938 startActivity(intent, optsBundle);
2939 } else {
2940 // TODO Component can be null when shortcuts are supported for secondary user
2941 launcherApps.startActivityForProfile(intent.getComponent(), user,
2942 intent.getSourceBounds(), optsBundle);
2943 }
2944 return true;
2945 } catch (SecurityException e) {
2946 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2947 Log.e(TAG, "Launcher does not have the permission to launch " + intent +
2948 ". Make sure to create a MAIN intent-filter for the corresponding activity " +
2949 "or use the exported attribute for this activity. "
2950 + "tag="+ tag + " intent=" + intent, e);
2951 }
2952 return false;
2953 }
2954
2955 boolean startActivitySafely(View v, Intent intent, Object tag) {
2956 boolean success = false;
2957 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
2958 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
2959 return false;
2960 }
2961 try {
2962 success = startActivity(v, intent, tag);
2963 } catch (ActivityNotFoundException e) {
2964 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
2965 Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
2966 }
2967 return success;
2968 }
2969
2970 /**
2971 * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
2972 * in the DragLayer in the exact absolute location of the original FolderIcon.
2973 */
2974 private void copyFolderIconToImage(FolderIcon fi) {
2975 final int width = fi.getMeasuredWidth();
2976 final int height = fi.getMeasuredHeight();
2977
2978 // Lazy load ImageView, Bitmap and Canvas
2979 if (mFolderIconImageView == null) {
2980 mFolderIconImageView = new ImageView(this);
2981 }
2982 if (mFolderIconBitmap == null || mFolderIconBitmap.getWidth() != width ||
2983 mFolderIconBitmap.getHeight() != height) {
2984 mFolderIconBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
2985 mFolderIconCanvas = new Canvas(mFolderIconBitmap);
2986 }
2987
2988 DragLayer.LayoutParams lp;
2989 if (mFolderIconImageView.getLayoutParams() instanceof DragLayer.LayoutParams) {
2990 lp = (DragLayer.LayoutParams) mFolderIconImageView.getLayoutParams();
2991 } else {
2992 lp = new DragLayer.LayoutParams(width, height);
2993 }
2994
2995 // The layout from which the folder is being opened may be scaled, adjust the starting
2996 // view size by this scale factor.
2997 float scale = mDragLayer.getDescendantRectRelativeToSelf(fi, mRectForFolderAnimation);
2998 lp.customPosition = true;
2999 lp.x = mRectForFolderAnimation.left;
3000 lp.y = mRectForFolderAnimation.top;
3001 lp.width = (int) (scale * width);
3002 lp.height = (int) (scale * height);
3003
3004 mFolderIconCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
3005 fi.draw(mFolderIconCanvas);
3006 mFolderIconImageView.setImageBitmap(mFolderIconBitmap);
3007 if (fi.getFolder() != null) {
3008 mFolderIconImageView.setPivotX(fi.getFolder().getPivotXForIconAnimation());
3009 mFolderIconImageView.setPivotY(fi.getFolder().getPivotYForIconAnimation());
3010 }
3011 // Just in case this image view is still in the drag layer from a previous animation,
3012 // we remove it and re-add it.
3013 if (mDragLayer.indexOfChild(mFolderIconImageView) != -1) {
3014 mDragLayer.removeView(mFolderIconImageView);
3015 }
3016 mDragLayer.addView(mFolderIconImageView, lp);
3017 if (fi.getFolder() != null) {
3018 fi.getFolder().bringToFront();
3019 }
3020 }
3021
3022 private void growAndFadeOutFolderIcon(FolderIcon fi) {
3023 if (fi == null) return;
3024 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0);
3025 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.5f);
3026 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.5f);
3027
3028 FolderInfo info = (FolderInfo) fi.getTag();
3029 if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3030 CellLayout cl = (CellLayout) fi.getParent().getParent();
3031 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) fi.getLayoutParams();
3032 cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
3033 }
3034
3035 // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
3036 copyFolderIconToImage(fi);
3037 fi.setVisibility(View.INVISIBLE);
3038
3039 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3040 scaleX, scaleY);
3041 if (Utilities.isLmpOrAbove()) {
3042 oa.setInterpolator(new LogDecelerateInterpolator(100, 0));
3043 }
3044 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3045 oa.start();
3046 }
3047
3048 private void shrinkAndFadeInFolderIcon(final FolderIcon fi) {
3049 if (fi == null) return;
3050 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f);
3051 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f);
3052 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f);
3053
3054 final CellLayout cl = (CellLayout) fi.getParent().getParent();
3055
3056 // We remove and re-draw the FolderIcon in-case it has changed
3057 mDragLayer.removeView(mFolderIconImageView);
3058 copyFolderIconToImage(fi);
3059 ObjectAnimator oa = LauncherAnimUtils.ofPropertyValuesHolder(mFolderIconImageView, alpha,
3060 scaleX, scaleY);
3061 oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
3062 oa.addListener(new AnimatorListenerAdapter() {
3063 @Override
3064 public void onAnimationEnd(Animator animation) {
3065 if (cl != null) {
3066 cl.clearFolderLeaveBehind();
3067 // Remove the ImageView copy of the FolderIcon and make the original visible.
3068 mDragLayer.removeView(mFolderIconImageView);
3069 fi.setVisibility(View.VISIBLE);
3070 }
3071 }
3072 });
3073 oa.start();
3074 }
3075
3076 /**
3077 * Opens the user folder described by the specified tag. The opening of the folder
3078 * is animated relative to the specified View. If the View is null, no animation
3079 * is played.
3080 *
3081 * @param folderInfo The FolderInfo describing the folder to open.
3082 */
3083 public void openFolder(FolderIcon folderIcon) {
3084 Folder folder = folderIcon.getFolder();
3085 FolderInfo info = folder.mInfo;
3086
3087 info.opened = true;
3088
3089 // Just verify that the folder hasn't already been added to the DragLayer.
3090 // There was a one-off crash where the folder had a parent already.
3091 if (folder.getParent() == null) {
3092 mDragLayer.addView(folder);
3093 mDragController.addDropTarget((DropTarget) folder);
3094 } else {
3095 Log.w(TAG, "Opening folder (" + folder + ") which already has a parent (" +
3096 folder.getParent() + ").");
3097 }
3098 folder.animateOpen();
3099 growAndFadeOutFolderIcon(folderIcon);
3100
3101 // Notify the accessibility manager that this folder "window" has appeared and occluded
3102 // the workspace items
3103 folder.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3104 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
3105 }
3106
3107 public void closeFolder() {
3108 Folder folder = mWorkspace != null ? mWorkspace.getOpenFolder() : null;
3109 if (folder != null) {
3110 if (folder.isEditingName()) {
3111 folder.dismissEditingName();
3112 }
3113 closeFolder(folder);
3114 }
3115 }
3116
3117 void closeFolder(Folder folder) {
3118 folder.getInfo().opened = false;
3119
3120 ViewGroup parent = (ViewGroup) folder.getParent().getParent();
3121 if (parent != null) {
3122 FolderIcon fi = (FolderIcon) mWorkspace.getViewForTag(folder.mInfo);
3123 shrinkAndFadeInFolderIcon(fi);
3124 }
3125 folder.animateClosed();
3126
3127 // Notify the accessibility manager that this folder "window" has disappeard and no
3128 // longer occludeds the workspace items
3129 getDragLayer().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3130 }
3131
3132 public boolean onLongClick(View v) {
3133 if (!isDraggingEnabled()) return false;
3134 if (isWorkspaceLocked()) return false;
3135 if (mState != State.WORKSPACE) return false;
3136
3137 if (v instanceof Workspace) {
3138 if (!mWorkspace.isInOverviewMode()) {
3139 if (mWorkspace.enterOverviewMode()) {
3140 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3141 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3142 return true;
3143 } else {
3144 return false;
3145 }
3146 } else {
3147 return false;
3148 }
3149 }
3150
3151 CellLayout.CellInfo longClickCellInfo = null;
3152 View itemUnderLongClick = null;
3153 if (v.getTag() instanceof ItemInfo) {
3154 ItemInfo info = (ItemInfo) v.getTag();
3155 longClickCellInfo = new CellLayout.CellInfo(v, info);;
3156 itemUnderLongClick = longClickCellInfo.cell;
3157 resetAddInfo();
3158 }
3159
3160 // The hotseat touch handling does not go through Workspace, and we always allow long press
3161 // on hotseat items.
3162 final boolean inHotseat = isHotseatLayout(v);
3163 boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
3164 if (allowLongPress && !mDragController.isDragging()) {
3165 if (itemUnderLongClick == null) {
3166 // User long pressed on empty space
3167 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
3168 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
3169 if (mWorkspace.isInOverviewMode()) {
3170 mWorkspace.startReordering(v);
3171 } else {
3172 mWorkspace.enterOverviewMode();
3173 }
3174 } else {
3175 final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
3176 mHotseat.getOrderInHotseat(
3177 longClickCellInfo.cellX,
3178 longClickCellInfo.cellY));
3179 if (!(itemUnderLongClick instanceof Folder || isAllAppsButton)) {
3180 // User long pressed on an item
3181 mWorkspace.startDrag(longClickCellInfo);
3182 }
3183 }
3184 }
3185 return true;
3186 }
3187
3188 boolean isHotseatLayout(View layout) {
3189 return mHotseat != null && layout != null &&
3190 (layout instanceof CellLayout) && (layout == mHotseat.getLayout());
3191 }
3192
3193 /**
3194 * Returns the CellLayout of the specified container at the specified screen.
3195 */
3196 CellLayout getCellLayout(long container, long screenId) {
3197 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
3198 if (mHotseat != null) {
3199 return mHotseat.getLayout();
3200 } else {
3201 return null;
3202 }
3203 } else {
3204 return (CellLayout) mWorkspace.getScreenWithId(screenId);
3205 }
3206 }
3207
3208 public boolean isAllAppsVisible() {
3209 return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
3210 }
3211
3212 private void setWorkspaceBackground(boolean workspace) {
3213 mLauncherView.setBackground(workspace ?
3214 mWorkspaceBackgroundDrawable : null);
3215 }
3216
3217 protected void changeWallpaperVisiblity(boolean visible) {
3218 int wpflags = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0;
3219 int curflags = getWindow().getAttributes().flags
3220 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
3221 if (wpflags != curflags) {
3222 getWindow().setFlags(wpflags, WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
3223 }
3224 setWorkspaceBackground(visible);
3225 }
3226
3227 private void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
3228 if (v instanceof LauncherTransitionable) {
3229 ((LauncherTransitionable) v).onLauncherTransitionPrepare(this, animated, toWorkspace);
3230 }
3231 }
3232
3233 private void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
3234 if (v instanceof LauncherTransitionable) {
3235 ((LauncherTransitionable) v).onLauncherTransitionStart(this, animated, toWorkspace);
3236 }
3237
3238 // Update the workspace transition step as well
3239 dispatchOnLauncherTransitionStep(v, 0f);
3240 }
3241
3242 private void dispatchOnLauncherTransitionStep(View v, float t) {
3243 if (v instanceof LauncherTransitionable) {
3244 ((LauncherTransitionable) v).onLauncherTransitionStep(this, t);
3245 }
3246 }
3247
3248 private void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
3249 if (v instanceof LauncherTransitionable) {
3250 ((LauncherTransitionable) v).onLauncherTransitionEnd(this, animated, toWorkspace);
3251 }
3252
3253 // Update the workspace transition step as well
3254 dispatchOnLauncherTransitionStep(v, 1f);
3255 }
3256
3257 /**
3258 * Things to test when changing the following seven functions.
3259 * - Home from workspace
3260 * - from center screen
3261 * - from other screens
3262 * - Home from all apps
3263 * - from center screen
3264 * - from other screens
3265 * - Back from all apps
3266 * - from center screen
3267 * - from other screens
3268 * - Launch app from workspace and quit
3269 * - with back
3270 * - with home
3271 * - Launch app from all apps and quit
3272 * - with back
3273 * - with home
3274 * - Go to a screen that's not the default, then all
3275 * apps, and launch and app, and go back
3276 * - with back
3277 * -with home
3278 * - On workspace, long press power and go back
3279 * - with back
3280 * - with home
3281 * - On all apps, long press power and go back
3282 * - with back
3283 * - with home
3284 * - On workspace, power off
3285 * - On all apps, power off
3286 * - Launch an app and turn off the screen while in that app
3287 * - Go back with home key
3288 * - Go back with back key TODO: make this not go to workspace
3289 * - From all apps
3290 * - From workspace
3291 * - Enter and exit car mode (becuase it causes an extra configuration changed)
3292 * - From all apps
3293 * - From the center workspace
3294 * - From another workspace
3295 */
3296
3297 /**
3298 * Zoom the camera out from the workspace to reveal 'toView'.
3299 * Assumes that the view to show is anchored at either the very top or very bottom
3300 * of the screen.
3301 */
3302 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded) {
3303 AppsCustomizePagedView.ContentType contentType = mAppsCustomizeContent.getContentType();
3304 showAppsCustomizeHelper(animated, springLoaded, contentType);
3305 }
3306
3307 private void showAppsCustomizeHelper(final boolean animated, final boolean springLoaded,
3308 final AppsCustomizePagedView.ContentType contentType) {
3309 if (mStateAnimation != null) {
3310 mStateAnimation.setDuration(0);
3311 mStateAnimation.cancel();
3312 mStateAnimation = null;
3313 }
3314
3315 boolean material = Utilities.isLmpOrAbove();
3316
3317 final Resources res = getResources();
3318
3319 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomInTime);
3320 final int fadeDuration = res.getInteger(R.integer.config_appsCustomizeFadeInTime);
3321 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
3322 final int itemsAlphaStagger =
3323 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
3324
3325 final float scale = (float) res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
3326 final View fromView = mWorkspace;
3327 final AppsCustomizeTabHost toView = mAppsCustomizeTabHost;
3328
3329 final ArrayList<View> layerViews = new ArrayList<View>();
3330
3331 Workspace.State workspaceState = contentType == AppsCustomizePagedView.ContentType.Widgets ?
3332 Workspace.State.OVERVIEW_HIDDEN : Workspace.State.NORMAL_HIDDEN;
3333 Animator workspaceAnim =
3334 mWorkspace.getChangeStateAnimation(workspaceState, animated, layerViews);
3335 if (!LauncherAppState.isDisableAllApps()
3336 || contentType == AppsCustomizePagedView.ContentType.Widgets) {
3337 // Set the content type for the all apps/widgets space
3338 mAppsCustomizeTabHost.setContentTypeImmediate(contentType);
3339 }
3340
3341 // If for some reason our views aren't initialized, don't animate
3342 boolean initialized = getAllAppsButton() != null;
3343
3344 if (animated && initialized) {
3345 mStateAnimation = LauncherAnimUtils.createAnimatorSet();
3346 final AppsCustomizePagedView content = (AppsCustomizePagedView)
3347 toView.findViewById(R.id.apps_customize_pane_content);
3348
3349 final View page = content.getPageAt(content.getCurrentPage());
3350 final View revealView = toView.findViewById(R.id.fake_page);
3351
3352 final float initialPanelAlpha = 1f;
3353
3354 final boolean isWidgetTray = contentType == AppsCustomizePagedView.ContentType.Widgets;
3355 if (isWidgetTray) {
3356 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
3357 } else {
3358 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
3359 }
3360
3361 // Hide the real page background, and swap in the fake one
3362 content.setPageBackgroundsVisible(false);
3363 revealView.setVisibility(View.VISIBLE);
3364 // We need to hide this view as the animation start will be posted.
3365 revealView.setAlpha(0);
3366
3367 int width = revealView.getMeasuredWidth();
3368 int height = revealView.getMeasuredHeight();
3369 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
3370
3371 revealView.setTranslationY(0);
3372 revealView.setTranslationX(0);
3373
3374 // Get the y delta between the center of the page and the center of the all apps button
3375 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
3376 getAllAppsButton(), null);
3377
3378 float alpha = 0;
3379 float xDrift = 0;
3380 float yDrift = 0;
3381 if (material) {
3382 alpha = isWidgetTray ? 0.3f : 1f;
3383 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
3384 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
3385 } else {
3386 yDrift = 2 * height / 3;
3387 xDrift = 0;
3388 }
3389 final float initAlpha = alpha;
3390
3391 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3392 layerViews.add(revealView);
3393 PropertyValuesHolder panelAlpha = PropertyValuesHolder.ofFloat("alpha", initAlpha, 1f);
3394 PropertyValuesHolder panelDriftY =
3395 PropertyValuesHolder.ofFloat("translationY", yDrift, 0);
3396 PropertyValuesHolder panelDriftX =
3397 PropertyValuesHolder.ofFloat("translationX", xDrift, 0);
3398
3399 ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
3400 panelAlpha, panelDriftY, panelDriftX);
3401
3402 panelAlphaAndDrift.setDuration(revealDuration);
3403 panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
3404
3405 mStateAnimation.play(panelAlphaAndDrift);
3406
3407 if (page != null) {
3408 page.setVisibility(View.VISIBLE);
3409 page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3410 layerViews.add(page);
3411
3412 ObjectAnimator pageDrift = ObjectAnimator.ofFloat(page, "translationY", yDrift, 0);
3413 page.setTranslationY(yDrift);
3414 pageDrift.setDuration(revealDuration);
3415 pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
3416 pageDrift.setStartDelay(itemsAlphaStagger);
3417 mStateAnimation.play(pageDrift);
3418
3419 page.setAlpha(0f);
3420 ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(page, "alpha", 0f, 1f);
3421 itemsAlpha.setDuration(revealDuration);
3422 itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
3423 itemsAlpha.setStartDelay(itemsAlphaStagger);
3424 mStateAnimation.play(itemsAlpha);
3425 }
3426
3427 View pageIndicators = toView.findViewById(R.id.apps_customize_page_indicator);
3428 pageIndicators.setAlpha(0.01f);
3429 ObjectAnimator indicatorsAlpha =
3430 ObjectAnimator.ofFloat(pageIndicators, "alpha", 1f);
3431 indicatorsAlpha.setDuration(revealDuration);
3432 mStateAnimation.play(indicatorsAlpha);
3433
3434 if (material) {
3435 final View allApps = getAllAppsButton();
3436 int allAppsButtonSize = LauncherAppState.getInstance().
3437 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
3438 float startRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
3439 Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2,
3440 height / 2, startRadius, revealRadius);
3441 reveal.setDuration(revealDuration);
3442 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
3443
3444 reveal.addListener(new AnimatorListenerAdapter() {
3445 public void onAnimationStart(Animator animation) {
3446 if (!isWidgetTray) {
3447 allApps.setVisibility(View.INVISIBLE);
3448 }
3449 }
3450 public void onAnimationEnd(Animator animation) {
3451 if (!isWidgetTray) {
3452 allApps.setVisibility(View.VISIBLE);
3453 }
3454 }
3455 });
3456 mStateAnimation.play(reveal);
3457 }
3458
3459 mStateAnimation.addListener(new AnimatorListenerAdapter() {
3460 @Override
3461 public void onAnimationEnd(Animator animation) {
3462 dispatchOnLauncherTransitionEnd(fromView, animated, false);
3463 dispatchOnLauncherTransitionEnd(toView, animated, false);
3464
3465 revealView.setVisibility(View.INVISIBLE);
3466 revealView.setLayerType(View.LAYER_TYPE_NONE, null);
3467 if (page != null) {
3468 page.setLayerType(View.LAYER_TYPE_NONE, null);
3469 }
3470 content.setPageBackgroundsVisible(true);
3471
3472 // Hide the search bar
3473 if (mSearchDropTargetBar != null) {
3474 mSearchDropTargetBar.hideSearchBar(false);
3475 }
3476 }
3477
3478 });
3479
3480 if (workspaceAnim != null) {
3481 mStateAnimation.play(workspaceAnim);
3482 }
3483
3484 dispatchOnLauncherTransitionPrepare(fromView, animated, false);
3485 dispatchOnLauncherTransitionPrepare(toView, animated, false);
3486 final AnimatorSet stateAnimation = mStateAnimation;
3487 final Runnable startAnimRunnable = new Runnable() {
3488 public void run() {
3489 // Check that mStateAnimation hasn't changed while
3490 // we waited for a layout/draw pass
3491 if (mStateAnimation != stateAnimation)
3492 return;
3493 dispatchOnLauncherTransitionStart(fromView, animated, false);
3494 dispatchOnLauncherTransitionStart(toView, animated, false);
3495
3496 revealView.setAlpha(initAlpha);
3497 if (Utilities.isLmpOrAbove()) {
3498 for (int i = 0; i < layerViews.size(); i++) {
3499 View v = layerViews.get(i);
3500 if (v != null) {
3501 boolean attached = true;
3502 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3503 attached = v.isAttachedToWindow();
3504 }
3505 if (attached) v.buildLayer();
3506 }
3507 }
3508 }
3509 mStateAnimation.start();
3510 }
3511 };
3512 toView.bringToFront();
3513 toView.setVisibility(View.VISIBLE);
3514 toView.post(startAnimRunnable);
3515 } else {
3516 toView.setTranslationX(0.0f);
3517 toView.setTranslationY(0.0f);
3518 toView.setScaleX(1.0f);
3519 toView.setScaleY(1.0f);
3520 toView.setVisibility(View.VISIBLE);
3521 toView.bringToFront();
3522
3523 if (!springLoaded && !LauncherAppState.getInstance().isScreenLarge()) {
3524 // Hide the search bar
3525 if (mSearchDropTargetBar != null) {
3526 mSearchDropTargetBar.hideSearchBar(false);
3527 }
3528 }
3529 dispatchOnLauncherTransitionPrepare(fromView, animated, false);
3530 dispatchOnLauncherTransitionStart(fromView, animated, false);
3531 dispatchOnLauncherTransitionEnd(fromView, animated, false);
3532 dispatchOnLauncherTransitionPrepare(toView, animated, false);
3533 dispatchOnLauncherTransitionStart(toView, animated, false);
3534 dispatchOnLauncherTransitionEnd(toView, animated, false);
3535 }
3536 }
3537
3538 /**
3539 * Zoom the camera back into the workspace, hiding 'fromView'.
3540 * This is the opposite of showAppsCustomizeHelper.
3541 * @param animated If true, the transition will be animated.
3542 */
3543 private void hideAppsCustomizeHelper(Workspace.State toState, final boolean animated,
3544 final boolean springLoaded, final Runnable onCompleteRunnable) {
3545
3546 if (mStateAnimation != null) {
3547 mStateAnimation.setDuration(0);
3548 mStateAnimation.cancel();
3549 mStateAnimation = null;
3550 }
3551
3552 boolean material = Utilities.isLmpOrAbove();
3553 Resources res = getResources();
3554
3555 final int duration = res.getInteger(R.integer.config_appsCustomizeZoomOutTime);
3556 final int fadeOutDuration = res.getInteger(R.integer.config_appsCustomizeFadeOutTime);
3557 final int revealDuration = res.getInteger(R.integer.config_appsCustomizeConcealTime);
3558 final int itemsAlphaStagger =
3559 res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
3560
3561 final float scaleFactor = (float)
3562 res.getInteger(R.integer.config_appsCustomizeZoomScaleFactor);
3563 final View fromView = mAppsCustomizeTabHost;
3564 final View toView = mWorkspace;
3565 Animator workspaceAnim = null;
3566 final ArrayList<View> layerViews = new ArrayList<View>();
3567
3568 if (toState == Workspace.State.NORMAL) {
3569 workspaceAnim = mWorkspace.getChangeStateAnimation(
3570 toState, animated, layerViews);
3571 } else if (toState == Workspace.State.SPRING_LOADED ||
3572 toState == Workspace.State.OVERVIEW) {
3573 workspaceAnim = mWorkspace.getChangeStateAnimation(
3574 toState, animated, layerViews);
3575 }
3576
3577 // If for some reason our views aren't initialized, don't animate
3578 boolean initialized = getAllAppsButton() != null;
3579
3580 if (animated && initialized) {
3581 mStateAnimation = LauncherAnimUtils.createAnimatorSet();
3582 if (workspaceAnim != null) {
3583 mStateAnimation.play(workspaceAnim);
3584 }
3585
3586 final AppsCustomizePagedView content = (AppsCustomizePagedView)
3587 fromView.findViewById(R.id.apps_customize_pane_content);
3588
3589 final View page = content.getPageAt(content.getNextPage());
3590
3591 // We need to hide side pages of the Apps / Widget tray to avoid some ugly edge cases
3592 int count = content.getChildCount();
3593 for (int i = 0; i < count; i++) {
3594 View child = content.getChildAt(i);
3595 if (child != page) {
3596 child.setVisibility(View.INVISIBLE);
3597 }
3598 }
3599 final View revealView = fromView.findViewById(R.id.fake_page);
3600
3601 // hideAppsCustomizeHelper is called in some cases when it is already hidden
3602 // don't perform all these no-op animations. In particularly, this was causing
3603 // the all-apps button to pop in and out.
3604 if (fromView.getVisibility() == View.VISIBLE) {
3605 AppsCustomizePagedView.ContentType contentType = content.getContentType();
3606 final boolean isWidgetTray =
3607 contentType == AppsCustomizePagedView.ContentType.Widgets;
3608
3609 if (isWidgetTray) {
3610 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
3611 } else {
3612 revealView.setBackground(res.getDrawable(R.drawable.quantum_panel));
3613 }
3614
3615 int width = revealView.getMeasuredWidth();
3616 int height = revealView.getMeasuredHeight();
3617 float revealRadius = (float) Math.sqrt((width * width) / 4 + (height * height) / 4);
3618
3619 // Hide the real page background, and swap in the fake one
3620 revealView.setVisibility(View.VISIBLE);
3621 content.setPageBackgroundsVisible(false);
3622
3623 final View allAppsButton = getAllAppsButton();
3624 revealView.setTranslationY(0);
3625 int[] allAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
3626 allAppsButton, null);
3627
3628 float xDrift = 0;
3629 float yDrift = 0;
3630 if (material) {
3631 yDrift = isWidgetTray ? height / 2 : allAppsToPanelDelta[1];
3632 xDrift = isWidgetTray ? 0 : allAppsToPanelDelta[0];
3633 } else {
3634 yDrift = 5 * height / 4;
3635 xDrift = 0;
3636 }
3637
3638 revealView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3639 TimeInterpolator decelerateInterpolator = material ?
3640 new LogDecelerateInterpolator(100, 0) :
3641 new LogDecelerateInterpolator(30, 0);
3642
3643 // The vertical motion of the apps panel should be delayed by one frame
3644 // from the conceal animation in order to give the right feel. We correpsondingly
3645 // shorten the duration so that the slide and conceal end at the same time.
3646 ObjectAnimator panelDriftY = LauncherAnimUtils.ofFloat(revealView, "translationY",
3647 0, yDrift);
3648 panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3649 panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3650 panelDriftY.setInterpolator(decelerateInterpolator);
3651 mStateAnimation.play(panelDriftY);
3652
3653 ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX",
3654 0, xDrift);
3655 panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3656 panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3657 panelDriftX.setInterpolator(decelerateInterpolator);
3658 mStateAnimation.play(panelDriftX);
3659
3660 if (isWidgetTray || !material) {
3661 float finalAlpha = material ? 0.4f : 0f;
3662 revealView.setAlpha(1f);
3663 ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha",
3664 1f, finalAlpha);
3665 panelAlpha.setDuration(revealDuration);
3666 panelAlpha.setInterpolator(material ? decelerateInterpolator :
3667 new AccelerateInterpolator(1.5f));
3668 mStateAnimation.play(panelAlpha);
3669 }
3670
3671 if (page != null) {
3672 page.setLayerType(View.LAYER_TYPE_HARDWARE, null);
3673
3674 ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(page, "translationY",
3675 0, yDrift);
3676 page.setTranslationY(0);
3677 pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
3678 pageDrift.setInterpolator(decelerateInterpolator);
3679 pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
3680 mStateAnimation.play(pageDrift);
3681
3682 page.setAlpha(1f);
3683 ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(page, "alpha", 1f, 0f);
3684 itemsAlpha.setDuration(100);
3685 itemsAlpha.setInterpolator(decelerateInterpolator);
3686 mStateAnimation.play(itemsAlpha);
3687 }
3688
3689 View pageIndicators = fromView.findViewById(R.id.apps_customize_page_indicator);
3690 pageIndicators.setAlpha(1f);
3691 ObjectAnimator indicatorsAlpha =
3692 LauncherAnimUtils.ofFloat(pageIndicators, "alpha", 0f);
3693 indicatorsAlpha.setDuration(revealDuration);
3694 indicatorsAlpha.setInterpolator(new DecelerateInterpolator(1.5f));
3695 mStateAnimation.play(indicatorsAlpha);
3696
3697 width = revealView.getMeasuredWidth();
3698
3699 if (material) {
3700 if (!isWidgetTray) {
3701 allAppsButton.setVisibility(View.INVISIBLE);
3702 }
3703 int allAppsButtonSize = LauncherAppState.getInstance().
3704 getDynamicGrid().getDeviceProfile().allAppsButtonVisualSize;
3705 float finalRadius = isWidgetTray ? 0 : allAppsButtonSize / 2;
3706 Animator reveal =
3707 LauncherAnimUtils.createCircularReveal(revealView, width / 2,
3708 height / 2, revealRadius, finalRadius);
3709 reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
3710 reveal.setDuration(revealDuration);
3711 reveal.setStartDelay(itemsAlphaStagger);
3712
3713 reveal.addListener(new AnimatorListenerAdapter() {
3714 public void onAnimationEnd(Animator animation) {
3715 revealView.setVisibility(View.INVISIBLE);
3716 if (!isWidgetTray) {
3717 allAppsButton.setVisibility(View.VISIBLE);
3718 }
3719 }
3720 });
3721
3722 mStateAnimation.play(reveal);
3723 }
3724
3725 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
3726 dispatchOnLauncherTransitionPrepare(toView, animated, true);
3727 mAppsCustomizeContent.stopScrolling();
3728 }
3729
3730 mStateAnimation.addListener(new AnimatorListenerAdapter() {
3731 @Override
3732 public void onAnimationEnd(Animator animation) {
3733 fromView.setVisibility(View.GONE);
3734 dispatchOnLauncherTransitionEnd(fromView, animated, true);
3735 dispatchOnLauncherTransitionEnd(toView, animated, true);
3736 if (onCompleteRunnable != null) {
3737 onCompleteRunnable.run();
3738 }
3739
3740 revealView.setLayerType(View.LAYER_TYPE_NONE, null);
3741 if (page != null) {
3742 page.setLayerType(View.LAYER_TYPE_NONE, null);
3743 }
3744 content.setPageBackgroundsVisible(true);
3745 // Unhide side pages
3746 int count = content.getChildCount();
3747 for (int i = 0; i < count; i++) {
3748 View child = content.getChildAt(i);
3749 child.setVisibility(View.VISIBLE);
3750 }
3751
3752 // Reset page transforms
3753 if (page != null) {
3754 page.setTranslationX(0);
3755 page.setTranslationY(0);
3756 page.setAlpha(1);
3757 }
3758 content.setCurrentPage(content.getNextPage());
3759
3760 mAppsCustomizeContent.updateCurrentPageScroll();
3761 }
3762 });
3763
3764 final AnimatorSet stateAnimation = mStateAnimation;
3765 final Runnable startAnimRunnable = new Runnable() {
3766 public void run() {
3767 // Check that mStateAnimation hasn't changed while
3768 // we waited for a layout/draw pass
3769 if (mStateAnimation != stateAnimation)
3770 return;
3771 dispatchOnLauncherTransitionStart(fromView, animated, false);
3772 dispatchOnLauncherTransitionStart(toView, animated, false);
3773
3774 if (Utilities.isLmpOrAbove()) {
3775 for (int i = 0; i < layerViews.size(); i++) {
3776 View v = layerViews.get(i);
3777 if (v != null) {
3778 boolean attached = true;
3779 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
3780 attached = v.isAttachedToWindow();
3781 }
3782 if (attached) v.buildLayer();
3783 }
3784 }
3785 }
3786 mStateAnimation.start();
3787 }
3788 };
3789 fromView.post(startAnimRunnable);
3790 } else {
3791 fromView.setVisibility(View.GONE);
3792 dispatchOnLauncherTransitionPrepare(fromView, animated, true);
3793 dispatchOnLauncherTransitionStart(fromView, animated, true);
3794 dispatchOnLauncherTransitionEnd(fromView, animated, true);
3795 dispatchOnLauncherTransitionPrepare(toView, animated, true);
3796 dispatchOnLauncherTransitionStart(toView, animated, true);
3797 dispatchOnLauncherTransitionEnd(toView, animated, true);
3798 }
3799 }
3800
3801 @Override
3802 public void onTrimMemory(int level) {
3803 super.onTrimMemory(level);
3804 if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
3805 mAppsCustomizeTabHost.onTrimMemory();
3806 }
3807 }
3808
3809 protected void showWorkspace(boolean animated) {
3810 showWorkspace(animated, null);
3811 }
3812
3813 protected void showWorkspace() {
3814 showWorkspace(true);
3815 }
3816
3817 void showWorkspace(boolean animated, Runnable onCompleteRunnable) {
3818 if (mState != State.WORKSPACE || mWorkspace.getState() != Workspace.State.NORMAL) {
3819 boolean wasInSpringLoadedMode = (mState != State.WORKSPACE);
3820 mWorkspace.setVisibility(View.VISIBLE);
3821 hideAppsCustomizeHelper(Workspace.State.NORMAL, animated, false, onCompleteRunnable);
3822
3823 // Show the search bar (only animate if we were showing the drop target bar in spring
3824 // loaded mode)
3825 if (mSearchDropTargetBar != null) {
3826 mSearchDropTargetBar.showSearchBar(animated && wasInSpringLoadedMode);
3827 }
3828
3829 // Set focus to the AppsCustomize button
3830 if (mAllAppsButton != null) {
3831 mAllAppsButton.requestFocus();
3832 }
3833 }
3834
3835 // Change the state *after* we've called all the transition code
3836 mState = State.WORKSPACE;
3837
3838 // Resume the auto-advance of widgets
3839 mUserPresent = true;
3840 updateRunning();
3841
3842 // Send an accessibility event to announce the context change
3843 getWindow().getDecorView()
3844 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3845
3846 onWorkspaceShown(animated);
3847 }
3848
3849 void showOverviewMode(boolean animated) {
3850 mWorkspace.setVisibility(View.VISIBLE);
3851 hideAppsCustomizeHelper(Workspace.State.OVERVIEW, animated, false, null);
3852 mState = State.WORKSPACE;
3853 onWorkspaceShown(animated);
3854 }
3855
3856 public void onWorkspaceShown(boolean animated) {
3857 }
3858
3859 void showAllApps(boolean animated, AppsCustomizePagedView.ContentType contentType,
3860 boolean resetPageToZero) {
3861 if (mState != State.WORKSPACE) return;
3862
3863 if (resetPageToZero) {
3864 mAppsCustomizeTabHost.reset();
3865 }
3866 showAppsCustomizeHelper(animated, false, contentType);
3867 mAppsCustomizeTabHost.post(new Runnable() {
3868 @Override
3869 public void run() {
3870 // We post this in-case the all apps view isn't yet constructed.
3871 mAppsCustomizeTabHost.requestFocus();
3872 }
3873 });
3874
3875 // Change the state *after* we've called all the transition code
3876 mState = State.APPS_CUSTOMIZE;
3877
3878 // Pause the auto-advance of widgets until we are out of AllApps
3879 mUserPresent = false;
3880 updateRunning();
3881 closeFolder();
3882
3883 // Send an accessibility event to announce the context change
3884 getWindow().getDecorView()
3885 .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
3886 }
3887
3888 void enterSpringLoadedDragMode() {
3889 if (isAllAppsVisible()) {
3890 hideAppsCustomizeHelper(Workspace.State.SPRING_LOADED, true, true, null);
3891 mState = State.APPS_CUSTOMIZE_SPRING_LOADED;
3892 }
3893 }
3894
3895 void exitSpringLoadedDragModeDelayed(final boolean successfulDrop, int delay,
3896 final Runnable onCompleteRunnable) {
3897 if (mState != State.APPS_CUSTOMIZE_SPRING_LOADED) return;
3898
3899 mHandler.postDelayed(new Runnable() {
3900 @Override
3901 public void run() {
3902 if (successfulDrop) {
3903 // Before we show workspace, hide all apps again because
3904 // exitSpringLoadedDragMode made it visible. This is a bit hacky; we should
3905 // clean up our state transition functions
3906 mAppsCustomizeTabHost.setVisibility(View.GONE);
3907 showWorkspace(true, onCompleteRunnable);
3908 } else {
3909 exitSpringLoadedDragMode();
3910 }
3911 }
3912 }, delay);
3913 }
3914
3915 void exitSpringLoadedDragMode() {
3916 if (mState == State.APPS_CUSTOMIZE_SPRING_LOADED) {
3917 final boolean animated = true;
3918 final boolean springLoaded = true;
3919 showAppsCustomizeHelper(animated, springLoaded);
3920 mState = State.APPS_CUSTOMIZE;
3921 }
3922 // Otherwise, we are not in spring loaded mode, so don't do anything.
3923 }
3924
3925 void lockAllApps() {
3926 // TODO
3927 }
3928
3929 void unlockAllApps() {
3930 // TODO
3931 }
3932
3933 /**
3934 * Hides the hotseat area.
3935 */
3936 void hideHotseat(boolean animated) {
3937 if (!LauncherAppState.getInstance().isScreenLarge()) {
3938 if (animated) {
3939 if (mHotseat.getAlpha() != 0f) {
3940 int duration = 0;
3941 if (mSearchDropTargetBar != null) {
3942 duration = mSearchDropTargetBar.getTransitionOutDuration();
3943 }
3944 mHotseat.animate().alpha(0f).setDuration(duration);
3945 }
3946 } else {
3947 mHotseat.setAlpha(0f);
3948 }
3949 }
3950 }
3951
3952 /**
3953 * Add an item from all apps or customize onto the given workspace screen.
3954 * If layout is null, add to the current screen.
3955 */
3956 void addExternalItemToScreen(ItemInfo itemInfo, final CellLayout layout) {
3957 if (!mWorkspace.addExternalItemToScreen(itemInfo, layout)) {
3958 showOutOfSpaceMessage(isHotseatLayout(layout));
3959 }
3960 }
3961
3962 /** Maps the current orientation to an index for referencing orientation correct global icons */
3963 private int getCurrentOrientationIndexForGlobalIcons() {
3964 // default - 0, landscape - 1
3965 switch (getResources().getConfiguration().orientation) {
3966 case Configuration.ORIENTATION_LANDSCAPE:
3967 return 1;
3968 default:
3969 return 0;
3970 }
3971 }
3972
3973 private Drawable getExternalPackageToolbarIcon(ComponentName activityName, String resourceName) {
3974 try {
3975 PackageManager packageManager = getPackageManager();
3976 // Look for the toolbar icon specified in the activity meta-data
3977 Bundle metaData = packageManager.getActivityInfo(
3978 activityName, PackageManager.GET_META_DATA).metaData;
3979 if (metaData != null) {
3980 int iconResId = metaData.getInt(resourceName);
3981 if (iconResId != 0) {
3982 Resources res = packageManager.getResourcesForActivity(activityName);
3983 return res.getDrawable(iconResId);
3984 }
3985 }
3986 } catch (NameNotFoundException e) {
3987 // This can happen if the activity defines an invalid drawable
3988 Log.w(TAG, "Failed to load toolbar icon; " + activityName.flattenToShortString() +
3989 " not found", e);
3990 } catch (Resources.NotFoundException nfe) {
3991 // This can happen if the activity defines an invalid drawable
3992 Log.w(TAG, "Failed to load toolbar icon from " + activityName.flattenToShortString(),
3993 nfe);
3994 }
3995 return null;
3996 }
3997
3998 // if successful in getting icon, return it; otherwise, set button to use default drawable
3999 private Drawable.ConstantState updateTextButtonWithIconFromExternalActivity(
4000 int buttonId, ComponentName activityName, int fallbackDrawableId,
4001 String toolbarResourceName) {
4002 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
4003 Resources r = getResources();
4004 int w = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_width);
4005 int h = r.getDimensionPixelSize(R.dimen.toolbar_external_icon_height);
4006
4007 TextView button = (TextView) findViewById(buttonId);
4008 // If we were unable to find the icon via the meta-data, use a generic one
4009 if (toolbarIcon == null) {
4010 toolbarIcon = r.getDrawable(fallbackDrawableId);
4011 toolbarIcon.setBounds(0, 0, w, h);
4012 if (button != null) {
4013 button.setCompoundDrawables(toolbarIcon, null, null, null);
4014 }
4015 return null;
4016 } else {
4017 toolbarIcon.setBounds(0, 0, w, h);
4018 if (button != null) {
4019 button.setCompoundDrawables(toolbarIcon, null, null, null);
4020 }
4021 return toolbarIcon.getConstantState();
4022 }
4023 }
4024
4025 // if successful in getting icon, return it; otherwise, set button to use default drawable
4026 private Drawable.ConstantState updateButtonWithIconFromExternalActivity(
4027 int buttonId, ComponentName activityName, int fallbackDrawableId,
4028 String toolbarResourceName) {
4029 ImageView button = (ImageView) findViewById(buttonId);
4030 Drawable toolbarIcon = getExternalPackageToolbarIcon(activityName, toolbarResourceName);
4031
4032 if (button != null) {
4033 // If we were unable to find the icon via the meta-data, use a
4034 // generic one
4035 if (toolbarIcon == null) {
4036 button.setImageResource(fallbackDrawableId);
4037 } else {
4038 button.setImageDrawable(toolbarIcon);
4039 }
4040 }
4041
4042 return toolbarIcon != null ? toolbarIcon.getConstantState() : null;
4043
4044 }
4045
4046 private void updateTextButtonWithDrawable(int buttonId, Drawable d) {
4047 TextView button = (TextView) findViewById(buttonId);
4048 button.setCompoundDrawables(d, null, null, null);
4049 }
4050
4051 private void updateButtonWithDrawable(int buttonId, Drawable.ConstantState d) {
4052 ImageView button = (ImageView) findViewById(buttonId);
4053 button.setImageDrawable(d.newDrawable(getResources()));
4054 }
4055
4056 private void invalidatePressedFocusedStates(View container, View button) {
4057 if (container instanceof HolographicLinearLayout) {
4058 HolographicLinearLayout layout = (HolographicLinearLayout) container;
4059 layout.invalidatePressedFocusedStates();
4060 } else if (button instanceof HolographicImageView) {
4061 HolographicImageView view = (HolographicImageView) button;
4062 view.invalidatePressedFocusedStates();
4063 }
4064 }
4065
4066 public View getQsbBar() {
4067 if (mQsb == null) {
4068 mQsb = mInflater.inflate(R.layout.qsb, mSearchDropTargetBar, false);
4069 mSearchDropTargetBar.addView(mQsb);
4070 }
4071 return mQsb;
4072 }
4073
4074 protected boolean updateGlobalSearchIcon() {
4075 final View searchButtonContainer = findViewById(R.id.search_button_container);
4076 final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
4077 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4078 final View voiceButton = findViewById(R.id.voice_button);
4079
4080 final SearchManager searchManager =
4081 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
4082 ComponentName activityName = searchManager.getGlobalSearchActivity();
4083 if (activityName != null) {
4084 int coi = getCurrentOrientationIndexForGlobalIcons();
4085 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4086 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
4087 TOOLBAR_SEARCH_ICON_METADATA_NAME);
4088 if (sGlobalSearchIcon[coi] == null) {
4089 sGlobalSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4090 R.id.search_button, activityName, R.drawable.ic_home_search_normal_holo,
4091 TOOLBAR_ICON_METADATA_NAME);
4092 }
4093
4094 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.VISIBLE);
4095 searchButton.setVisibility(View.VISIBLE);
4096 invalidatePressedFocusedStates(searchButtonContainer, searchButton);
4097 return true;
4098 } else {
4099 // We disable both search and voice search when there is no global search provider
4100 if (searchButtonContainer != null) searchButtonContainer.setVisibility(View.GONE);
4101 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
4102 if (searchButton != null) searchButton.setVisibility(View.GONE);
4103 if (voiceButton != null) voiceButton.setVisibility(View.GONE);
4104 updateVoiceButtonProxyVisible(false);
4105 return false;
4106 }
4107 }
4108
4109 protected void updateGlobalSearchIcon(Drawable.ConstantState d) {
4110 final View searchButtonContainer = findViewById(R.id.search_button_container);
4111 final View searchButton = (ImageView) findViewById(R.id.search_button);
4112 updateButtonWithDrawable(R.id.search_button, d);
4113 invalidatePressedFocusedStates(searchButtonContainer, searchButton);
4114 }
4115
4116 protected boolean updateVoiceSearchIcon(boolean searchVisible) {
4117 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4118 final View voiceButton = findViewById(R.id.voice_button);
4119
4120 // We only show/update the voice search icon if the search icon is enabled as well
4121 final SearchManager searchManager =
4122 (SearchManager) getSystemService(Context.SEARCH_SERVICE);
4123 ComponentName globalSearchActivity = searchManager.getGlobalSearchActivity();
4124
4125 ComponentName activityName = null;
4126 if (globalSearchActivity != null) {
4127 // Check if the global search activity handles voice search
4128 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
4129 intent.setPackage(globalSearchActivity.getPackageName());
4130 activityName = intent.resolveActivity(getPackageManager());
4131 }
4132
4133 if (activityName == null) {
4134 // Fallback: check if an activity other than the global search activity
4135 // resolves this
4136 Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
4137 activityName = intent.resolveActivity(getPackageManager());
4138 }
4139 if (searchVisible && activityName != null) {
4140 int coi = getCurrentOrientationIndexForGlobalIcons();
4141 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4142 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
4143 TOOLBAR_VOICE_SEARCH_ICON_METADATA_NAME);
4144 if (sVoiceSearchIcon[coi] == null) {
4145 sVoiceSearchIcon[coi] = updateButtonWithIconFromExternalActivity(
4146 R.id.voice_button, activityName, R.drawable.ic_home_voice_search_holo,
4147 TOOLBAR_ICON_METADATA_NAME);
4148 }
4149 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.VISIBLE);
4150 voiceButton.setVisibility(View.VISIBLE);
4151 updateVoiceButtonProxyVisible(false);
4152 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
4153 return true;
4154 } else {
4155 if (voiceButtonContainer != null) voiceButtonContainer.setVisibility(View.GONE);
4156 if (voiceButton != null) voiceButton.setVisibility(View.GONE);
4157 updateVoiceButtonProxyVisible(false);
4158 return false;
4159 }
4160 }
4161
4162 protected void updateVoiceSearchIcon(Drawable.ConstantState d) {
4163 final View voiceButtonContainer = findViewById(R.id.voice_button_container);
4164 final View voiceButton = findViewById(R.id.voice_button);
4165 updateButtonWithDrawable(R.id.voice_button, d);
4166 invalidatePressedFocusedStates(voiceButtonContainer, voiceButton);
4167 }
4168
4169 public void updateVoiceButtonProxyVisible(boolean forceDisableVoiceButtonProxy) {
4170 final View voiceButtonProxy = findViewById(R.id.voice_button_proxy);
4171 if (voiceButtonProxy != null) {
4172 boolean visible = !forceDisableVoiceButtonProxy &&
4173 mWorkspace.shouldVoiceButtonProxyBeVisible();
4174 voiceButtonProxy.setVisibility(visible ? View.VISIBLE : View.GONE);
4175 voiceButtonProxy.bringToFront();
4176 }
4177 }
4178
4179 /**
4180 * This is an overrid eot disable the voice button proxy. If disabled is true, then the voice button🔵
4181 * will be hidden regardless of what shouldVoiceButtonProxyBeVisible() returns.
4182 */
4183 public void disableVoiceButtonProxy(boolean disabled) {
4184 updateVoiceButtonProxyVisible(disabled);
4185 }
4186
4187 @Override
4188 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
4189 final boolean result = super.dispatchPopulateAccessibilityEvent(event);
4190 final List<CharSequence> text = event.getText();
4191 text.clear();
4192 // Populate event with a fake title based on the current state.
4193 if (mState == State.APPS_CUSTOMIZE) {
4194 text.add(mAppsCustomizeTabHost.getContentTag());
4195 } else {
4196 text.add(getString(R.string.all_apps_home_button_label));
4197 }
4198 return result;
4199 }
4200
4201 /**
4202 * Receives notifications when system dialogs are to be closed.
4203 */
4204 private class CloseSystemDialogsIntentReceiver extends BroadcastReceiver {
4205 @Override
4206 public void onReceive(Context context, Intent intent) {
4207 closeSystemDialogs();
4208 }
4209 }
4210
4211 /**
4212 * Receives notifications whenever the appwidgets are reset.
4213 */
4214 private class AppWidgetResetObserver extends ContentObserver {
4215 public AppWidgetResetObserver() {
4216 super(new Handler());
4217 }
4218
4219 @Override
4220 public void onChange(boolean selfChange) {
4221 onAppWidgetReset();
4222 }
4223 }
4224
4225 /**
4226 * If the activity is currently paused, signal that we need to run the passed Runnable
4227 * in onResume.
4228 *
4229 * This needs to be called from incoming places where resources might have been loaded
4230 * while we are paused. That is becaues the Configuration might be wrong
4231 * when we're not running, and if it comes back to what it was when we
4232 * were paused, we are not restarted.
4233 *
4234 * Implementation of the method from LauncherModel.Callbacks.
4235 *
4236 * @return true if we are currently paused. The caller might be able to
4237 * skip some work in that case since we will come back again.
4238 */
4239 private boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
4240 if (mPaused) {
4241 Log.i(TAG, "Deferring update until onResume");
4242 if (deletePreviousRunnables) {
4243 while (mBindOnResumeCallbacks.remove(run)) {
4244 }
4245 }
4246 mBindOnResumeCallbacks.add(run);
4247 return true;
4248 } else {
4249 return false;
4250 }
4251 }
4252
4253 private boolean waitUntilResume(Runnable run) {
4254 return waitUntilResume(run, false);
4255 }
4256
4257 public void addOnResumeCallback(Runnable run) {
4258 mOnResumeCallbacks.add(run);
4259 }
4260
4261 /**
4262 * If the activity is currently paused, signal that we need to re-run the loader
4263 * in onResume.
4264 *
4265 * This needs to be called from incoming places where resources might have been loaded
4266 * while we are paused. That is becaues the Configuration might be wrong
4267 * when we're not running, and if it comes back to what it was when we
4268 * were paused, we are not restarted.
4269 *
4270 * Implementation of the method from LauncherModel.Callbacks.
4271 *
4272 * @return true if we are currently paused. The caller might be able to
4273 * skip some work in that case since we will come back again.
4274 */
4275 public boolean setLoadOnResume() {
4276 if (mPaused) {
4277 Log.i(TAG, "setLoadOnResume");
4278 mOnResumeNeedsLoad = true;
4279 return true;
4280 } else {
4281 return false;
4282 }
4283 }
4284
4285 /**
4286 * Implementation of the method from LauncherModel.Callbacks.
4287 */
4288 public int getCurrentWorkspaceScreen() {
4289 if (mWorkspace != null) {
4290 return mWorkspace.getCurrentPage();
4291 } else {
4292 return SCREEN_COUNT / 2;
4293 }
4294 }
4295
4296 /**
4297 * Refreshes the shortcuts shown on the workspace.
4298 *
4299 * Implementation of the method from LauncherModel.Callbacks.
4300 */
4301 public void startBinding() {
4302 setWorkspaceLoading(true);
4303
4304 // If we're starting binding all over again, clear any bind calls we'd postponed in
4305 // the past (see waitUntilResume) -- we don't need them since we're starting binding
4306 // from scratch again
4307 mBindOnResumeCallbacks.clear();
4308
4309 // Clear the workspace because it's going to be rebound
4310 mWorkspace.clearDropTargets();
4311 mWorkspace.removeAllWorkspaceScreens();
4312
4313 mWidgetsToAdvance.clear();
4314 if (mHotseat != null) {
4315 mHotseat.resetLayout();
4316 }
4317 }
4318
4319 @Override
4320 public void bindScreens(ArrayList<Long> orderedScreenIds) {
4321 bindAddScreens(orderedScreenIds);
4322
4323 // If there are no screens, we need to have an empty screen
4324 if (orderedScreenIds.size() == 0) {
4325 mWorkspace.addExtraEmptyScreen();
4326 }
4327
4328 // Create the custom content page (this call updates mDefaultScreen which calls
4329 // setCurrentPage() so ensure that all pages are added before calling this).
4330 if (hasCustomContentToLeft()) {
4331 mWorkspace.createCustomContentContainer();
4332 populateCustomContentContainer();
4333 }
4334 }
4335
4336 @Override
4337 public void bindAddScreens(ArrayList<Long> orderedScreenIds) {
4338 // Log to disk
4339 Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true);
4340 Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " +
4341 TextUtils.join(", ", orderedScreenIds), true);
4342 int count = orderedScreenIds.size();
4343 for (int i = 0; i < count; i++) {
4344 mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
4345 }
4346 }
4347
4348 private boolean shouldShowWeightWatcher() {
4349 String spKey = LauncherAppState.getSharedPreferencesKey();
4350 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4351 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, SHOW_WEIGHT_WATCHER_DEFAULT);
4352
4353 return show;
4354 }
4355
4356 private void toggleShowWeightWatcher() {
4357 String spKey = LauncherAppState.getSharedPreferencesKey();
4358 SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE);
4359 boolean show = sp.getBoolean(SHOW_WEIGHT_WATCHER, true);
4360
4361 show = !show;
4362
4363 SharedPreferences.Editor editor = sp.edit();
4364 editor.putBoolean(SHOW_WEIGHT_WATCHER, show);
4365 editor.commit();
4366
4367 if (mWeightWatcher != null) {
4368 mWeightWatcher.setVisibility(show ? View.VISIBLE : View.GONE);
4369 }
4370 }
4371
4372 public void bindAppsAdded(final ArrayList<Long> newScreens,
4373 final ArrayList<ItemInfo> addNotAnimated,
4374 final ArrayList<ItemInfo> addAnimated,
4375 final ArrayList<AppInfo> addedApps) {
4376 Runnable r = new Runnable() {
4377 public void run() {
4378 bindAppsAdded(newScreens, addNotAnimated, addAnimated, addedApps);
4379 }
4380 };
4381 if (waitUntilResume(r)) {
4382 return;
4383 }
4384
4385 // Add the new screens
4386 if (newScreens != null) {
4387 bindAddScreens(newScreens);
4388 }
4389
4390 // We add the items without animation on non-visible pages, and with
4391 // animations on the new page (which we will try and snap to).
4392 if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
4393 bindItems(addNotAnimated, 0,
4394 addNotAnimated.size(), false);
4395 }
4396 if (addAnimated != null && !addAnimated.isEmpty()) {
4397 bindItems(addAnimated, 0,
4398 addAnimated.size(), true);
4399 }
4400
4401 // Remove the extra empty screen
4402 mWorkspace.removeExtraEmptyScreen(false, false);
4403
4404 if (!LauncherAppState.isDisableAllApps() &&
4405 addedApps != null && mAppsCustomizeContent != null) {
4406 mAppsCustomizeContent.addApps(addedApps);
4407 }
4408 }
4409
4410 /**
4411 * Bind the items start-end from the list.
4412 *
4413 * Implementation of the method from LauncherModel.Callbacks.
4414 */
4415 public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end,
4416 final boolean forceAnimateIcons) {
4417 Runnable r = new Runnable() {
4418 public void run() {
4419 bindItems(shortcuts, start, end, forceAnimateIcons);
4420 }
4421 };
4422 if (waitUntilResume(r)) {
4423 return;
4424 }
4425
4426 // Get the list of added shortcuts and intersect them with the set of shortcuts here
4427 final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
4428 final Collection<Animator> bounceAnims = new ArrayList<Animator>();
4429 final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
4430 Workspace workspace = mWorkspace;
4431 long newShortcutsScreenId = -1;
4432 for (int i = start; i < end; i++) {
4433 final ItemInfo item = shortcuts.get(i);
4434
4435 // Short circuit if we are loading dock items for a configuration which has no dock
4436 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
4437 mHotseat == null) {
4438 continue;
4439 }
4440
4441 switch (item.itemType) {
4442 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
4443 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
4444 ShortcutInfo info = (ShortcutInfo) item;
4445 View shortcut = createShortcut(info);
4446
4447 /*
4448 * TODO: FIX collision case
4449 */
4450 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
4451 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
4452 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
4453 View v = cl.getChildAt(item.cellX, item.cellY);
4454 Object tag = v.getTag();
4455 String desc = "Collision while binding workspace item: " + item
4456 + ". Collides with " + tag;
4457 if (LauncherAppState.isDogfoodBuild()) {
4458 throw (new RuntimeException(desc));
4459 } else {
4460 Log.d(TAG, desc);
4461 }
4462 }
4463 }
4464
4465 workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX,
4466 item.cellY, 1, 1);
4467 if (animateIcons) {
4468 // Animate all the applications up now
4469 shortcut.setAlpha(0f);
4470 shortcut.setScaleX(0f);
4471 shortcut.setScaleY(0f);
4472 bounceAnims.add(createNewAppBounceAnimation(shortcut, i));
4473 newShortcutsScreenId = item.screenId;
4474 }
4475 break;
4476 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
4477 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
4478 (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
4479 (FolderInfo) item, mIconCache);
4480 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX,
4481 item.cellY, 1, 1);
4482 break;
4483 default:
4484 throw new RuntimeException("Invalid Item Type");
4485 }
4486 }
4487
4488 if (animateIcons) {
4489 // Animate to the correct page
4490 if (newShortcutsScreenId > -1) {
4491 long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
4492 final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId);
4493 final Runnable startBounceAnimRunnable = new Runnable() {
4494 public void run() {
4495 anim.playTogether(bounceAnims);
4496 anim.start();
4497 }
4498 };
4499 if (newShortcutsScreenId != currentScreenId) {
4500 // We post the animation slightly delayed to prevent slowdowns
4501 // when we are loading right after we return to launcher.
4502 mWorkspace.postDelayed(new Runnable() {
4503 public void run() {
4504 if (mWorkspace != null) {
4505 mWorkspace.snapToPage(newScreenIndex);
4506 mWorkspace.postDelayed(startBounceAnimRunnable,
4507 NEW_APPS_ANIMATION_DELAY);
4508 }
4509 }
4510 }, NEW_APPS_PAGE_MOVE_DELAY);
4511 } else {
4512 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
4513 }
4514 }
4515 }
4516 workspace.requestLayout();
4517 }
4518
4519 /**
4520 * Implementation of the method from LauncherModel.Callbacks.
4521 */
4522 public void bindFolders(final HashMap<Long, FolderInfo> folders) {
4523 Runnable r = new Runnable() {
4524 public void run() {
4525 bindFolders(folders);
4526 }
4527 };
4528 if (waitUntilResume(r)) {
4529 return;
4530 }
4531 sFolders.clear();
4532 sFolders.putAll(folders);
4533 }
4534
4535 /**
4536 * Add the views for a widget to the workspace.
4537 *
4538 * Implementation of the method from LauncherModel.Callbacks.
4539 */
4540 public void bindAppWidget(final LauncherAppWidgetInfo item) {
4541 Runnable r = new Runnable() {
4542 public void run() {
4543 bindAppWidget(item);
4544 }
4545 };
4546 if (waitUntilResume(r)) {
4547 return;
4548 }
4549
4550 final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0;
4551 if (DEBUG_WIDGETS) {
4552 Log.d(TAG, "bindAppWidget: " + item);
4553 }
4554 final Workspace workspace = mWorkspace;
4555
4556 AppWidgetProviderInfo appWidgetInfo;
4557 if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) &&
4558 ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) {
4559
4560 appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);
4561 if (appWidgetInfo == null) {
4562 if (DEBUG_WIDGETS) {
4563 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4564 + " belongs to component " + item.providerName
4565 + ", as the povider is null");
4566 }
4567 LauncherModel.deleteItemFromDatabase(this, item);
4568 return;
4569 }
4570 // Note: This assumes that the id remap broadcast is received before this step.
4571 // If that is not the case, the id remap will be ignored and user may see the
4572 // click to setup view.
4573 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);
4574 pendingInfo.spanX = item.spanX;
4575 pendingInfo.spanY = item.spanY;
4576 pendingInfo.minSpanX = item.minSpanX;
4577 pendingInfo.minSpanY = item.minSpanY;
4578 Bundle options =
4579 AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);
4580
4581 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();
4582 boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
4583 newWidgetId, appWidgetInfo, options);
4584
4585 // TODO consider showing a permission dialog when the widget is clicked.
4586 if (!success) {
4587 mAppWidgetHost.deleteAppWidgetId(newWidgetId);
4588 if (DEBUG_WIDGETS) {
4589 Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId
4590 + " belongs to component " + item.providerName
4591 + ", as the launcher is unable to bing a new widget id");
4592 }
4593 LauncherModel.deleteItemFromDatabase(this, item);
4594 return;
4595 }
4596
4597 item.appWidgetId = newWidgetId;
4598
4599 // If the widget has a configure activity, it is still needs to set it up, otherwise
4600 // the widget is ready to go.
4601 item.restoreStatus = (appWidgetInfo.configure == null)
4602 ? LauncherAppWidgetInfo.RESTORE_COMPLETED
4603 : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
4604
4605 LauncherModel.updateItemInDatabase(this, item);
4606 }
4607
4608 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
4609 final int appWidgetId = item.appWidgetId;
4610 appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
4611 if (DEBUG_WIDGETS) {
4612 Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidget🔵
4613 }
4614
4615 item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
4616 } else {
4617 appWidgetInfo = null;
4618 PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item);
4619 view.updateIcon(mIconCache);
4620 item.hostView = view;
4621 item.hostView.updateAppWidget(null);
4622 item.hostView.setOnClickListener(this);
4623 }
4624
4625 item.hostView.setTag(item);
4626 item.onBindAppWidget(this);
4627
4628 workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX,
4629 item.cellY, item.spanX, item.spanY, false);
4630 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
4631
4632 workspace.requestLayout();
4633
4634 if (DEBUG_WIDGETS) {
4635 Log.d(TAG, "bound widget id="+item.appWidgetId+" in "
4636 + (SystemClock.uptimeMillis()-start) + "ms");
4637 }
4638 }
4639
4640 /**
4641 * Restores a pending widget.
4642 *
4643 * @param appWidgetId The app widget id
4644 * @param cellInfo The position on screen where to create the widget.
4645 */
4646 private void completeRestoreAppWidget(final int appWidgetId) {
4647 LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
4648 if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
4649 Log.e(TAG, "Widget update called, when the widget no longer exists.");
4650 return;
4651 }
4652
4653 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
4654 info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
4655
4656 mWorkspace.reinflateWidgetsIfNecessary();
4657 LauncherModel.updateItemInDatabase(this, info);
4658 }
4659
4660 public void onPageBoundSynchronously(int page) {
4661 mSynchronouslyBoundPages.add(page);
4662 }
4663
4664 /**
4665 * Callback saying that there aren't any more items to bind.
4666 *
4667 * Implementation of the method from LauncherModel.Callbacks.
4668 */
4669 public void finishBindingItems(final boolean upgradePath) {
4670 Runnable r = new Runnable() {
4671 public void run() {
4672 finishBindingItems(upgradePath);
4673 }
4674 };
4675 if (waitUntilResume(r)) {
4676 return;
4677 }
4678 if (mSavedState != null) {
4679 if (!mWorkspace.hasFocus()) {
4680 mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus();
4681 }
4682 mSavedState = null;
4683 }
4684 mWorkspace.restoreInstanceStateForRemainingPages();
4685 setWorkspaceLoading(false);
4686 sendLoadingCompleteBroadcastIfNecessary();
4687 // If we received the result of any pending adds while the loader was running (e.g. the
4688 // widget configuration forced an orientation change), process them now.
4689 if (sPendingAddItem != null) {
4690 final long screenId = completeAdd(sPendingAddItem);
4691 // TODO: this moves the user to the page where the pending item was added. Ideally,
4692 // the screen would be guaranteed to exist after bind, and the page would be set through
4693 // the workspace restore process.
4694 mWorkspace.post(new Runnable() {
4695 @Override
4696 public void run() {
4697 mWorkspace.snapToScreenId(screenId);
4698 }
4699 });
4700 sPendingAddItem = null;
4701 }
4702 if (upgradePath) {
4703 mWorkspace.getUniqueComponents(true, null);
4704 mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
4705 }
4706 PackageInstallerCompat.getInstance(this).onFinishBind();
4707 mModel.recheckRestoredItems(this);
4708 }
4709
4710 private void sendLoadingCompleteBroadcastIfNecessary() {
4711 if (!mSharedPrefs.getBoolean(FIRST_LOAD_COMPLETE, false)) {
4712 String permission =
4713 getResources().getString(R.string.receive_first_load_broadcast_permission);
4714 Intent intent = new Intent(ACTION_FIRST_LOAD_COMPLETE);
4715 sendBroadcast(intent, permission);
4716 SharedPreferences.Editor editor = mSharedPrefs.edit();
4717 editor.putBoolean(FIRST_LOAD_COMPLETE, true);
4718 editor.apply();
4719 }
4720 }
4721
4722 public boolean isAllAppsButtonRank(int rank) {
4723 if (mHotseat != null) {
4724 return mHotseat.isAllAppsButtonRank(rank);
4725 }
4726 return false;
4727 }
4728
4729 private boolean canRunNewAppsAnimation() {
4730 long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
4731 return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
4732 }
4733
4734 private ValueAnimator createNewAppBounceAnimation(View v, int i) {
4735 ValueAnimator bounceAnim = LauncherAnimUtils.ofPropertyValuesHolder(v,
4736 PropertyValuesHolder.ofFloat("alpha", 1f),
4737 PropertyValuesHolder.ofFloat("scaleX", 1f),
4738 PropertyValuesHolder.ofFloat("scaleY", 1f));
4739 bounceAnim.setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
4740 bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
4741 bounceAnim.setInterpolator(new SmoothPagedView.OvershootInterpolator());
4742 return bounceAnim;
4743 }
4744
4745 public boolean useVerticalBarLayout() {
4746 return LauncherAppState.getInstance().getDynamicGrid().
4747 getDeviceProfile().isVerticalBarLayout();
4748 }
4749
4750 protected Rect getSearchBarBounds() {
4751 return LauncherAppState.getInstance().getDynamicGrid().
4752 getDeviceProfile().getSearchBarBounds();
4753 }
4754
4755 @Override
4756 public void bindSearchablesChanged() {
4757 boolean searchVisible = updateGlobalSearchIcon();
4758 boolean voiceVisible = updateVoiceSearchIcon(searchVisible);
4759 if (mSearchDropTargetBar != null) {
4760 mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);
4761 }
4762 }
4763
4764 /**
4765 * Add the icons for all apps.
4766 *
4767 * Implementation of the method from LauncherModel.Callbacks.
4768 */
4769 public void bindAllApplications(final ArrayList<AppInfo> apps) {
4770 if (LauncherAppState.isDisableAllApps()) {
4771 if (mIntentsOnWorkspaceFromUpgradePath != null) {
4772 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
4773 getHotseat().addAllAppsFolder(mIconCache, apps,
4774 mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
4775 }
4776 mIntentsOnWorkspaceFromUpgradePath = null;
4777 }
4778 if (mAppsCustomizeContent != null) {
4779 mAppsCustomizeContent.onPackagesUpdated(
4780 LauncherModel.getSortedWidgetsAndShortcuts(this));
4781 }
4782 } else {
4783 if (mAppsCustomizeContent != null) {
4784 mAppsCustomizeContent.setApps(apps);
4785 mAppsCustomizeContent.onPackagesUpdated(
4786 LauncherModel.getSortedWidgetsAndShortcuts(this));
4787 }
4788 }
4789 }
4790
4791 /**
4792 * A package was updated.
4793 *
4794 * Implementation of the method from LauncherModel.Callbacks.
4795 */
4796 public void bindAppsUpdated(final ArrayList<AppInfo> apps) {
4797 Runnable r = new Runnable() {
4798 public void run() {
4799 bindAppsUpdated(apps);
4800 }
4801 };
4802 if (waitUntilResume(r)) {
4803 return;
4804 }
4805
4806 if (mWorkspace != null) {
4807 mWorkspace.updateShortcutsAndWidgets(apps);
4808 }
4809
4810 if (!LauncherAppState.isDisableAllApps() &&
4811 mAppsCustomizeContent != null) {
4812 mAppsCustomizeContent.updateApps(apps);
4813 }
4814 }
4815
4816 /**
4817 * Some shortcuts were updated in the background.
4818 *
4819 * Implementation of the method from LauncherModel.Callbacks.
4820 */
4821 public void bindShortcutsUpdated(final ArrayList<ShortcutInfo> shortcuts) {
4822 Runnable r = new Runnable() {
4823 public void run() {
4824 bindShortcutsUpdated(shortcuts);
4825 }
4826 };
4827 if (waitUntilResume(r)) {
4828 return;
4829 }
4830
4831 if (mWorkspace != null) {
4832 mWorkspace.updateShortcuts(shortcuts);
4833 }
4834 }
4835
4836 /**
4837 * Packages were restored
4838 */
4839 public void bindAppsRestored(final ArrayList<AppInfo> apps) {
4840 Runnable r = new Runnable() {
4841 public void run() {
4842 bindAppsRestored(apps);
4843 }
4844 };
4845 if (waitUntilResume(r)) {
4846 return;
4847 }
4848
4849 if (mWorkspace != null) {
4850 mWorkspace.updateShortcutsAndWidgets(apps);
4851 }
4852 }
4853
4854 /**
4855 * Update the state of a package, typically related to install state.
4856 *
4857 * Implementation of the method from LauncherModel.Callbacks.
4858 */
4859 @Override
4860 public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
4861 if (mWorkspace != null) {
4862 mWorkspace.updatePackageState(installInfo);
4863 }
4864 }
4865
4866 /**
4867 * Update the label and icon of all the icons in a package
4868 *
4869 * Implementation of the method from LauncherModel.Callbacks.
4870 */
4871 @Override
4872 public void updatePackageBadge(String packageName) {
4873 if (mWorkspace != null) {
4874 mWorkspace.updatePackageBadge(packageName, UserHandleCompat.myUserHandle());
4875 }
4876 }
4877
4878 /**
4879 * A package was uninstalled. We take both the super set of packageNames
4880 * in addition to specific applications to remove, the reason being that
4881 * this can be called when a package is updated as well. In that scenario,
4882 * we only remove specific components from the workspace, where as
4883 * package-removal should clear all items by package name.
4884 *
4885 * @param reason
4886 * if non-zero, the icons are not permanently removed, rather marked as disabled.
4887 * Implementation of the method from LauncherModel.Callbacks.
4888 */
4889 @Override
4890 public void bindComponentsRemoved(final ArrayList<String> packageNames, final ArrayList<AppInfo> appI🔵
4891 Runnable r = new Runnable() {
4892 public void run() {
4893 bindComponentsRemoved(packageNames, appInfos, user, reason);
4894 }
4895 };
4896 if (waitUntilResume(r)) {
4897 return;
4898 }
4899 if (reason == 0) {
4900 if (!packageNames.isEmpty()) {
4901 mWorkspace.removeItemsByPackageName(packageNames, user);
4902 }
4903 if (!appInfos.isEmpty()) {
4904 mWorkspace.removeItemsByApplicationInfo(appInfos, user);
4905 }
4906 } else {
4907 mWorkspace.disableShortcutsByPackageName(packageNames, user, reason);
4908 }
4909 // Notify the drag controller
4910 mDragController.onAppsRemoved(packageNames, appInfos);
4911 // Update AllApps
4912 if ((!LauncherAppState.isDisableAllApps()) && (mAppsCustomizeContent != null)) {
4913 mAppsCustomizeContent.removeApps(appInfos);
4914 }
4915 }
4916
4917 /**
4918 * A number of packages were updated.
4919 */
4920 private ArrayList<Object> mWidgetsAndShortcuts;
4921
4922 private Runnable mBindPackagesUpdatedRunnable = new Runnable() {
4923 public void run() {
4924 bindPackagesUpdated(mWidgetsAndShortcuts);
4925 mWidgetsAndShortcuts = null;
4926 }
4927 };
4928
4929 public void bindPackagesUpdated(final ArrayList<Object> widgetsAndShortcuts) {
4930 if (waitUntilResume(mBindPackagesUpdatedRunnable, true)) {
4931 mWidgetsAndShortcuts = widgetsAndShortcuts;
4932 return;
4933 }
4934
4935 // Update the widgets pane
4936 if (mAppsCustomizeContent != null) {
4937 mAppsCustomizeContent.onPackagesUpdated(widgetsAndShortcuts);
4938 }
4939 }
4940
4941 private int mapConfigurationOriActivityInfoOri(int configOri) {
4942 final Display d = getWindowManager().getDefaultDisplay();
4943 int naturalOri = Configuration.ORIENTATION_LANDSCAPE;
4944 switch (d.getRotation()) {
4945 case Surface.ROTATION_0:
4946 case Surface.ROTATION_180:
4947 // We are currently in the same basic orientation as the natural orientation
4948 naturalOri = configOri;
4949 break;
4950 case Surface.ROTATION_90:
4951 case Surface.ROTATION_270:
4952 // We are currently in the other basic orientation to the natural orientation
4953 naturalOri = (configOri == Configuration.ORIENTATION_LANDSCAPE) ?
4954 Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
4955 break;
4956 }
4957
4958 int[] oriMap = {
4959 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
4960 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
4961 ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
4962 ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
4963 };
4964 // Since the map starts at portrait, we need to offset if this device's natural orientation
4965 // is landscape.
4966 int indexOffset = 0;
4967 if (naturalOri == Configuration.ORIENTATION_LANDSCAPE) {
4968 indexOffset = 1;
4969 }
4970 return oriMap[(d.getRotation() + indexOffset) % 4];
4971 }
4972
4973 public boolean isRotationEnabled() {
4974 boolean enableRotation = sForceEnableRotation ||
4975 getResources().getBoolean(R.bool.allow_rotation);
4976 return enableRotation;
4977 }
4978
4979 public void lockScreenOrientation() {
4980 if (isRotationEnabled()) {
4981 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
4982 .getConfiguration().orientation));
4983 }
4984 }
4985
4986 public void unlockScreenOrientation(boolean immediate) {
4987 if (isRotationEnabled()) {
4988 if (immediate) {
4989 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4990 } else {
4991 mHandler.postDelayed(new Runnable() {
4992 public void run() {
4993 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
4994 }
4995 }, mRestoreScreenOrientationDelay);
4996 }
4997 }
4998 }
4999
5000 /**
5001 * Called when the SearchBar hint should be changed.
5002 *
5003 * @param hint the hint to be displayed in the search bar.
5004 */
5005 protected void onSearchBarHintChanged(String hint) {
5006
5007 }
5008
5009 protected boolean isLauncherPreinstalled() {
5010 PackageManager pm = getPackageManager();
5011 try {
5012 ApplicationInfo ai = pm.getApplicationInfo(getComponentName().getPackageName(), 0);
5013 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
5014 return true;
5015 } else {
5016 return false;
5017 }
5018 } catch (NameNotFoundException e) {
5019 e.printStackTrace();
5020 return false;
5021 }
5022 }
5023
5024 /**
5025 * This method indicates whether or not we should suggest default wallpaper dimensions
5026 * when our wallpaper cropper was not yet used to set a wallpaper.
5027 */
5028 protected boolean overrideWallpaperDimensions() {
5029 return true;
5030 }
5031
5032 /**
5033 * To be overridden by subclasses to indicate that there is an activity to launch
5034 * before showing the standard launcher experience.
5035 */
5036 protected boolean hasFirstRunActivity() {
5037 return false;
5038 }
5039
5040 /**
5041 * To be overridden by subclasses to launch any first run activity
5042 */
5043 protected Intent getFirstRunActivity() {
5044 return null;
5045 }
5046
5047 private boolean shouldRunFirstRunActivity() {
5048 return !ActivityManager.isRunningInTestHarness() &&
5049 !mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
5050 }
5051
5052 protected boolean hasRunFirstRunActivity() {
5053 return mSharedPrefs.getBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, false);
5054 }
5055
5056 public boolean showFirstRunActivity() {
5057 if (shouldRunFirstRunActivity() &&
5058 hasFirstRunActivity()) {
5059 Intent firstRunIntent = getFirstRunActivity();
5060 if (firstRunIntent != null) {
5061 startActivity(firstRunIntent);
5062 markFirstRunActivityShown();
5063 return true;
5064 }
5065 }
5066 return false;
5067 }
5068
5069 private void markFirstRunActivityShown() {
5070 SharedPreferences.Editor editor = mSharedPrefs.edit();
5071 editor.putBoolean(FIRST_RUN_ACTIVITY_DISPLAYED, true);
5072 editor.apply();
5073 }
5074
5075 /**
5076 * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
5077 * screen that must be displayed and dismissed.
5078 */
5079 protected boolean hasDismissableIntroScreen() {
5080 return false;
5081 }
5082
5083 /**
5084 * Full screen intro screen to be shown and dismissed before the launcher can be used.
5085 */
5086 protected View getIntroScreen() {
5087 return null;
5088 }
5089
5090 /**
5091 * To be overriden by subclasses to indicate whether the in-activity intro screen has been
5092 * dismissed. This method is ignored if #hasDismissableIntroScreen returns false.
5093 */
5094 private boolean shouldShowIntroScreen() {
5095 return hasDismissableIntroScreen() &&
5096 !mSharedPrefs.getBoolean(INTRO_SCREEN_DISMISSED, false);
5097 }
5098
5099 protected void showIntroScreen() {
5100 View introScreen = getIntroScreen();
5101 changeWallpaperVisiblity(false);
5102 if (introScreen != null) {
5103 mDragLayer.showOverlayView(introScreen);
5104 }
5105 }
5106
5107 public void dismissIntroScreen() {
5108 markIntroScreenDismissed();
5109 if (showFirstRunActivity()) {
5110 // We delay hiding the intro view until the first run activity is showing. This
5111 // avoids a blip.
5112 mWorkspace.postDelayed(new Runnable() {
5113 @Override
5114 public void run() {
5115 mDragLayer.dismissOverlayView();
5116 showFirstRunClings();
5117 }
5118 }, ACTIVITY_START_DELAY);
5119 } else {
5120 mDragLayer.dismissOverlayView();
5121 showFirstRunClings();
5122 }
5123 changeWallpaperVisiblity(true);
5124 }
5125
5126 private void markIntroScreenDismissed() {
5127 SharedPreferences.Editor editor = mSharedPrefs.edit();
5128 editor.putBoolean(INTRO_SCREEN_DISMISSED, true);
5129 editor.apply();
5130 }
5131
5132 private void showFirstRunClings() {
5133 // The two first run cling paths are mutually exclusive, if the launcher is preinstalled
5134 // on the device, then we always show the first run cling experience (or if there is no
5135 // launcher2). Otherwise, we prompt the user upon started for migration
5136 LauncherClings launcherClings = new LauncherClings(this);
5137 if (launcherClings.shouldShowFirstRunOrMigrationClings()) {
5138 if (mModel.canMigrateFromOldLauncherDb(this)) {
5139 launcherClings.showMigrationCling();
5140 } else {
5141 launcherClings.showLongPressCling(true);
5142 }
5143 }
5144 }
5145
5146 void showWorkspaceSearchAndHotseat() {
5147 if (mWorkspace != null) mWorkspace.setAlpha(1f);
5148 if (mHotseat != null) mHotseat.setAlpha(1f);
5149 if (mPageIndicators != null) mPageIndicators.setAlpha(1f);
5150 if (mSearchDropTargetBar != null) mSearchDropTargetBar.showSearchBar(false);
5151 }
5152
5153 void hideWorkspaceSearchAndHotseat() {
5154 if (mWorkspace != null) mWorkspace.setAlpha(0f);
5155 if (mHotseat != null) mHotseat.setAlpha(0f);
5156 if (mPageIndicators != null) mPageIndicators.setAlpha(0f);
5157 if (mSearchDropTargetBar != null) mSearchDropTargetBar.hideSearchBar(false);
5158 }
5159
5160 public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
5161 // Called from search suggestion, not supported in other profiles.
5162 final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
5163 LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
5164 LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
5165 myUser);
5166 if (activityInfo == null) {
5167 return null;
5168 }
5169 return new AppInfo(this, activityInfo, myUser, mIconCache, null);
5170 }
5171
5172 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5173 Bitmap icon) {
5174 // Called from search suggestion, not supported in other profiles.
5175 return createShortcutDragInfo(shortcutIntent, caption, icon,
5176 UserHandleCompat.myUserHandle());
5177 }
5178
5179 public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
5180 Bitmap icon, UserHandleCompat user) {
5181 UserManagerCompat userManager = UserManagerCompat.getInstance(this);
5182 CharSequence contentDescription = userManager.getBadgedLabelForUser(caption, user);
5183 return new ShortcutInfo(shortcutIntent, caption, contentDescription, icon, user);
5184 }
5185
5186 protected void moveWorkspaceToDefaultScreen() {
5187 mWorkspace.moveToDefaultScreen(false);
5188 }
5189
5190 public void startDrag(View dragView, ItemInfo dragInfo, DragSource source) {
5191 dragView.setTag(dragInfo);
5192 mWorkspace.onExternalDragStartedWithItem(dragView);
5193 mWorkspace.beginExternalDragShared(dragView, source);
5194 }
5195
5196 @Override
5197 public void onPageSwitch(View newPage, int newPageIndex) {
5198 }
5199
5200 /**
5201 * Prints out out state for debugging.
5202 */
5203 public void dumpState() {
5204 Log.d(TAG, "BEGIN launcher3 dump state for launcher " + this);
5205 Log.d(TAG, "mSavedState=" + mSavedState);
5206 Log.d(TAG, "mWorkspaceLoading=" + mWorkspaceLoading);
5207 Log.d(TAG, "mRestoring=" + mRestoring);
5208 Log.d(TAG, "mWaitingForResult=" + mWaitingForResult);
5209 Log.d(TAG, "mSavedInstanceState=" + mSavedInstanceState);
5210 Log.d(TAG, "sFolders.size=" + sFolders.size());
5211 mModel.dumpState();
5212
5213 if (mAppsCustomizeContent != null) {
5214 mAppsCustomizeContent.dumpState();
5215 }
5216 Log.d(TAG, "END launcher3 dump state");
5217 }
5218
5219 @Override
5220 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
5221 super.dump(prefix, fd, writer, args);
5222 synchronized (sDumpLogs) {
5223 writer.println(" ");
5224 writer.println("Debug logs: ");
5225 for (int i = 0; i < sDumpLogs.size(); i++) {
5226 writer.println(" " + sDumpLogs.get(i));
5227 }
5228 }
5229 }
5230
5231 public static void dumpDebugLogsToConsole() {
5232 if (DEBUG_DUMP_LOG) {
5233 synchronized (sDumpLogs) {
5234 Log.d(TAG, "");
5235 Log.d(TAG, "*********************");
5236 Log.d(TAG, "Launcher debug logs: ");
5237 for (int i = 0; i < sDumpLogs.size(); i++) {
5238 Log.d(TAG, " " + sDumpLogs.get(i));
5239 }
5240 Log.d(TAG, "*********************");
5241 Log.d(TAG, "");
5242 }
5243 }
5244 }
5245
5246 public static void addDumpLog(String tag, String log, boolean debugLog) {
5247 addDumpLog(tag, log, null, debugLog);
5248 }
5249
5250 public static void addDumpLog(String tag, String log, Exception e, boolean debugLog) {
5251 if (debugLog) {
5252 if (e != null) {
5253 Log.d(tag, log, e);
5254 } else {
5255 Log.d(tag, log);
5256 }
5257 }
5258 if (DEBUG_DUMP_LOG) {
5259 sDateStamp.setTime(System.currentTimeMillis());
5260 synchronized (sDumpLogs) {
5261 sDumpLogs.add(sDateFormat.format(sDateStamp) + ": " + tag + ", " + log
5262 + (e == null ? "" : (", Exception: " + e)));
5263 }
5264 }
5265 }
5266
5267 public void dumpLogsToLocalData() {
5268 if (DEBUG_DUMP_LOG) {
5269 new AsyncTask<Void, Void, Void>() {
5270 public Void doInBackground(Void ... args) {
5271 boolean success = false;
5272 sDateStamp.setTime(sRunStart);
5273 String FILENAME = sDateStamp.getMonth() + "-"
5274 + sDateStamp.getDay() + "_"
5275 + sDateStamp.getHours() + "-"
5276 + sDateStamp.getMinutes() + "_"
5277 + sDateStamp.getSeconds() + ".txt";
5278
5279 FileOutputStream fos = null;
5280 File outFile = null;
5281 try {
5282 outFile = new File(getFilesDir(), FILENAME);
5283 outFile.createNewFile();
5284 fos = new FileOutputStream(outFile);
5285 } catch (Exception e) {
5286 e.printStackTrace();
5287 }
5288 if (fos != null) {
5289 PrintWriter writer = new PrintWriter(fos);
5290
5291 writer.println(" ");
5292 writer.println("Debug logs: ");
5293 synchronized (sDumpLogs) {
5294 for (int i = 0; i < sDumpLogs.size(); i++) {
5295 writer.println(" " + sDumpLogs.get(i));
5296 }
5297 }
5298 writer.close();
5299 }
5300 try {
5301 if (fos != null) {
5302 fos.close();
5303 success = true;
5304 }
5305 } catch (IOException e) {
5306 e.printStackTrace();
5307 }
5308 return null;
5309 }
5310 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
5311 }
5312 }
5313 }
5314
5315 interface LauncherTransitionable {
5316 public abstract View getContent();
5317
5318 public abstract void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace);
5319
5320 public abstract void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace);
5321
5322 public abstract void onLauncherTransitionStep(Launcher l, float t);
5323
5324 public abstract void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace);
5325 } |